Skip to content

Conversation

lachlancollins
Copy link
Member

@lachlancollins lachlancollins commented Sep 26, 2025

🎯 Changes

Breaking update to the Svelte adapter, which switches from stores to runes syntax.

Tremendous thanks to @zhihengGet , @elliott-with-the-longest-name-on-github , and everyone else who has contributed or tested this adapter!

✅ Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested this code locally with pnpm test:pr.

🚀 Release Impact

  • This change affects published code, and I have generated a changeset.
  • This change is docs/CI/dev-only (no release).

Summary by CodeRabbit

  • Breaking Changes

    • Requires Svelte >= 5.25 (runes); config objects must be provided as thunks (() => …). Templates use direct query/mutation access (query.*). Several components removed exported props in favor of prop-destructuring/children rendering.
  • New Features

    • New reactive helpers (ref/box), hooks: useIsFetching, useIsMutating, useMutationState, and grouped Devtools options; QueryClientProvider default export.
  • Documentation

    • Added "Migrate from v5 to v6" guide; legacy reactivity doc removed; Svelte docs updated.
  • Chores / Tests

    • Peer deps tightened to Svelte 5.25+, ESLint/parser updates, broad test additions and example migrations.

zhihengGet and others added 13 commits April 12, 2025 12:11
* feat: Draft proposal

* chore: Improve reactive containers

* ci: apply automated fixes

* oops

* fix: Update API, add a bunch of tests

* merge main

* fix: use const

* more tests

* feat: More tests, back to thunks, fixed svelte-query-persist-client

* feat: More tests and examples!

* lockfile

* fixes

* Fix current CI errors

* More small fixes/tweaks

* Remove test.only

* ci: apply automated fixes

* Fix pnpm-lock, fix import order

* update main docs

* feat: More tests

* ci: apply automated fixes

* add back old tests

* Cleanup

* Fix persist client

* Fix useMutationState

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Lachlan Collins <[email protected]>
Copy link

changeset-bot bot commented Sep 26, 2025

🦋 Changeset detected

Latest commit: f3fc6a2

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@tanstack/svelte-query-persist-client Major
@tanstack/svelte-query-devtools Major
@tanstack/svelte-query Major

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copy link
Contributor

coderabbitai bot commented Sep 26, 2025

Walkthrough

Migration of the Svelte adapter and examples to Svelte "runes": option objects become thunk/accessor functions, store auto-subscriptions replaced with ref/Box/ReactiveValue patterns, many .ts implementations replaced by .svelte variants, props consumed via $props(), and peer deps/tooling updated to require Svelte ^5.25.0.

Changes

Cohort / File(s) Summary
Changeset
.changeset/pink-pots-jam.md
Adds a breaking-changes entry documenting major @tanstack/svelte-query version bumps and linking migration guidance.
Docs & nav
docs/config.json, docs/framework/svelte/installation.md, docs/framework/svelte/overview.md, docs/framework/svelte/reactivity.md (removed), docs/framework/svelte/ssr.md, docs/framework/svelte/migrate-from-v5-to-v6.md
Add migration page and nav entry; remove legacy reactivity doc; update examples/docs to show thunked options and non-$ access; update usage notes and export lists.
Tooling & ESLint
eslint.config.js, packages/*/eslint.config.js, packages/svelte-query-devtools/eslint.config.js
Add @typescript-eslint/parser import, switch Svelte ESLint preset to recommended, include .svelte.ts/.svelte.js globs, set parser to tsParser and extraFileExtensions.
Package metadata
packages/*/package.json, packages/svelte-query/package.json, packages/svelte-query-devtools/package.json, packages/svelte-query-persist-client/package.json
Add keywords/devDeps, update peerDependency svelte to ^5.25.0.
Svelte config (runes)
examples/svelte/*/svelte.config.js, packages/*/svelte.config.js, packages/svelte-query-devtools/svelte.config.js
Enable compilerOptions.runes = true across examples and packages.
Layouts / children rendering
Many examples/svelte/**/src/routes/+layout.svelte, packages/svelte-query/src/HydrationBoundary.svelte, packages/svelte-query/src/QueryClientProvider.svelte
Replace <slot /> with {@render children()} and extract children via $props().
Examples — call-site changes
examples/svelte/** (many +page.svelte, components)
Convert createQuery/createMutation/createInfiniteQuery/createQueries calls to thunk/accessor () => ({ ... }), replace $store auto-subscriptions with direct query.*, adopt $state and $props patterns, update event bindings (e.g., on:...onclick).
Examples — playground stores
examples/svelte/playground/src/lib/stores.svelte.ts, removed examples/svelte/playground/src/lib/stores.ts, plus many consumers
Replace writable store module with .svelte ref-style exports (ref/.value); update imports and consumers to use .value.
CSS adjustments
examples/svelte/load-more-infinite-scroll/src/app.css
Replace button selectors with .button class selectors.
Core types & exports
packages/svelte-query/src/types.ts, packages/svelte-query/src/index.ts, packages/svelte-query/src/context.ts
Add Accessor<T> and Box/ReactiveValue types, remove StoreOrVal/Readable wrappers, switch context keys to Symbols, re-export .svelte.js variants, add QueryClientProvider .svelte export.
Core: runes implementations (added)
packages/svelte-query/src/*.svelte.ts (createBaseQuery.svelte.ts, createMutation.svelte.ts, createQueries.svelte.ts, useIsFetching.svelte.ts, useIsMutating.svelte.ts, useMutationState.svelte.ts, utils.svelte.ts, containers.svelte.ts, utils.svelte.ts/watchChanges)
New runes-aware implementations accepting Accessor thunks, using watchChanges/untrack/$effect, returning ReactiveValue/Box/ref-style outputs; new containers primitives (ReactiveValue, createRawRef).
Core: legacy removals
packages/svelte-query/src/*.ts (createBaseQuery.ts, createMutation.ts, createQueries.ts variants, useIsFetching.ts, useIsMutating.ts, useMutationState.ts, utils.ts, etc.)
Remove legacy store-based implementations replaced by .svelte variants.
Containers & primitives
packages/svelte-query/src/containers.svelte.ts, packages/svelte-query/tests/containers.svelte.test.ts
Add createRawRef, ReactiveValue, and Box types and comprehensive tests for raw-ref/reactivity behavior.
Persist-client provider & utils
packages/svelte-query-persist-client/src/PersistQueryClientProvider.svelte, packages/svelte-query-persist-client/src/utils.svelte.ts
Replace exported props with $props() destructuring, add box<T> util, switch restoration/subscription to effect-driven flow, render children via QueryClientProvider.
Devtools
packages/svelte-query-devtools/src/Devtools.svelte, packages/svelte-query-devtools/package.json
Collapse many exported props into a DevtoolsOptions object via $props(), add onMount cleanup and $effect updates; update peer svelte and package metadata.
Tests & test utilities
packages/svelte-query/tests/**, packages/svelte-query/tests/utils.svelte.ts, packages/svelte-query-persist-client/tests/**
Add runes-based test utilities (ref, promiseWithResolvers, withEffectRoot), convert many tests/components to thunked options/ref-style state, add/update many tests (createQuery, createQueries), remove older store-based test fragments, introduce ProviderWrapper and test components for useIsFetching/useIsMutating.
Vite / test config
packages/*/vite.config.ts
Adjust coverage/restoreMocks testing config (coverage toggles removed/disabled).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant C as Component
  participant A as Options Accessor
  participant QC as QueryClient Accessor
  participant O as QueryObserver
  participant R as Reactive Result

  C->>A: invoke () => options
  C->>QC: resolve QueryClient (if accessor)
  C->>O: instantiate QueryObserver(client, resolvedOptions)
  O-->>R: produce initial (optimistic) result
  loop runtime updates
    O-->>R: notify -> update reactive result
    C->>C: template reads result.* (no `$` prefix)
    A->>O: options change -> pre-effect setOptions
  end
Loading
sequenceDiagram
  autonumber
  participant P as PersistQueryClientProvider
  participant B as Box<boolean> (isRestoring)
  participant Rf as restore()
  participant Sb as subscribe()
  participant QP as QueryClientProvider

  P->>B: set isRestoring.current = true
  P->>Rf: call persistQueryClientRestore(client, opts)
  alt success
    Rf-->>P: resolved
    P->>B: set isRestoring.current = false
    P->>Sb: call persistQueryClientSubscribe(client, opts)
  else failure
    Rf-->>P: rejected
    P->>B: set isRestoring.current = false
  end
  P->>QP: render children via QueryClientProvider
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Suggested reviewers

  • TkDodo

Poem

I thump my whiskers on the keys tonight,
Thunks and boxes make the code feel light.
No dollar signs in template song,
Children render, props move along.
A hopping rabbit—v6 takes flight! 🐇✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed The pull request description adheres to the repository’s template by including the “## 🎯 Changes” section with a clear summary of the breaking update, the completed “## ✅ Checklist” reflecting adherence to contribution and testing steps, and the “## 🚀 Release Impact” section with accurate checkboxes for published code and changeset generation. All required sections are present and properly filled out.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
Title Check ✅ Passed The title clearly and concisely summarizes the primary intent of the pull request—rewriting the Svelte adapter to use runes—and properly scopes the change to the svelte-query package while indicating a breaking feature enhancement. It avoids extraneous file lists or vague terminology, making the main change immediately understandable to reviewers.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch svelte-5-adapter

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

nx-cloud bot commented Sep 26, 2025

View your CI Pipeline Execution ↗ for commit f3fc6a2

Command Status Duration Result
nx affected --targets=test:sherif,test:knip,tes... ✅ Succeeded 2m 24s View ↗
nx run-many --target=build --exclude=examples/*... ✅ Succeeded 7s View ↗

☁️ Nx Cloud last updated this comment at 2025-09-30 12:02:17 UTC

Copy link

pkg-pr-new bot commented Sep 26, 2025

More templates

@tanstack/angular-query-experimental

npm i https://pkg.pr.new/@tanstack/angular-query-experimental@9694

@tanstack/eslint-plugin-query

npm i https://pkg.pr.new/@tanstack/eslint-plugin-query@9694

@tanstack/query-async-storage-persister

npm i https://pkg.pr.new/@tanstack/query-async-storage-persister@9694

@tanstack/query-broadcast-client-experimental

npm i https://pkg.pr.new/@tanstack/query-broadcast-client-experimental@9694

@tanstack/query-core

npm i https://pkg.pr.new/@tanstack/query-core@9694

@tanstack/query-devtools

npm i https://pkg.pr.new/@tanstack/query-devtools@9694

@tanstack/query-persist-client-core

npm i https://pkg.pr.new/@tanstack/query-persist-client-core@9694

@tanstack/query-sync-storage-persister

npm i https://pkg.pr.new/@tanstack/query-sync-storage-persister@9694

@tanstack/react-query

npm i https://pkg.pr.new/@tanstack/react-query@9694

@tanstack/react-query-devtools

npm i https://pkg.pr.new/@tanstack/react-query-devtools@9694

@tanstack/react-query-next-experimental

npm i https://pkg.pr.new/@tanstack/react-query-next-experimental@9694

@tanstack/react-query-persist-client

npm i https://pkg.pr.new/@tanstack/react-query-persist-client@9694

@tanstack/solid-query

npm i https://pkg.pr.new/@tanstack/solid-query@9694

@tanstack/solid-query-devtools

npm i https://pkg.pr.new/@tanstack/solid-query-devtools@9694

@tanstack/solid-query-persist-client

npm i https://pkg.pr.new/@tanstack/solid-query-persist-client@9694

@tanstack/svelte-query

npm i https://pkg.pr.new/@tanstack/svelte-query@9694

@tanstack/svelte-query-devtools

npm i https://pkg.pr.new/@tanstack/svelte-query-devtools@9694

@tanstack/svelte-query-persist-client

npm i https://pkg.pr.new/@tanstack/svelte-query-persist-client@9694

@tanstack/vue-query

npm i https://pkg.pr.new/@tanstack/vue-query@9694

@tanstack/vue-query-devtools

npm i https://pkg.pr.new/@tanstack/vue-query-devtools@9694

commit: f3fc6a2

Copy link
Contributor

github-actions bot commented Sep 26, 2025

Sizes for commit f3fc6a2:

Branch Bundle Size
Main
This PR

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 23

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/svelte-query-devtools/src/Devtools.svelte (1)

66-89: Fix race: async import may resolve after unmount, causing stray mount

If the component unmounts before the dynamic import resolves, devtools can still instantiate/mount into a detached node. Add a cancellation guard and unmount the local instance safely.

Apply:

-    onMount(() => {
-      import('@tanstack/query-devtools').then((m) => {
-        const QueryDevtools = m.TanstackQueryDevtools
-
-        devtools = new QueryDevtools({
-          client,
-          queryFlavor: 'Svelte Query',
-          version: '5',
-          onlineManager,
-          buttonPosition,
-          position,
-          initialIsOpen,
-          errorTypes,
-          styleNonce,
-          shadowDOMTarget,
-          hideDisabledQueries,
-        })
-
-        devtools.mount(ref)
-      })
-      return () => devtools?.unmount()
-    })
+    onMount(() => {
+      let cancelled = false
+      let instance: TanstackQueryDevtools | undefined
+
+      ;(async () => {
+        const m = await import('@tanstack/query-devtools')
+        if (cancelled) return
+        const QueryDevtools = m.TanstackQueryDevtools
+
+        instance = new QueryDevtools({
+          client,
+          queryFlavor: 'Svelte Query',
+          version: '5',
+          onlineManager,
+          buttonPosition,
+          position,
+          initialIsOpen,
+          errorTypes,
+          styleNonce,
+          shadowDOMTarget,
+          hideDisabledQueries,
+        })
+
+        devtools = instance
+        instance.mount(ref)
+      })()
+
+      return () => {
+        cancelled = true
+        instance?.unmount()
+      }
+    })
packages/svelte-query/src/createQuery.ts (1)

15-38: Adjust all overloads to accept a nullable queryClient accessor.

For consistency with the implementation (and createBaseQuery), the overload signatures should accept Accessor<QueryClient | undefined> rather than Accessor<QueryClient>. Without this, TypeScript will complain when an accessor conditionally returns undefined—despite the runtime handling it. Update each overload’s queryClient parameter accordingly.

-  queryClient?: Accessor<QueryClient>,
+  queryClient?: Accessor<QueryClient | undefined>,

(Apply the same change to every overload that declares queryClient.)

🧹 Nitpick comments (67)
packages/svelte-query-devtools/package.json (1)

57-58: Keep Svelte peer ranges aligned across packages.

To reduce confusion for consumers, let’s mirror the adapter’s svelte peer range (^5.7.0). It keeps the expectation consistent and avoids scenarios where the devtools appear to accept lower minors than the core package actually supports.

packages/svelte-query-persist-client/eslint.config.js (1)

20-24: Limit the blanket svelte/no-unused-svelte-ignore disable.

Turning this rule off repo-wide removes our guardrail against stale @svelte-ignore comments. If the disable is only here to dodge current eslint-plugin-svelte false positives with runes, please either scope it to the specific files that hit the issue or leave an inline TODO pointing to the upstream tracker so we remember to re-enable it. Right now it’s a silent, permanent relax.

examples/svelte/star-wars/src/routes/films/[filmId]/Character.svelte (2)

11-14: Pass AbortSignal, guard when id is empty, and avoid subtle closure mismatch

Improve cancellation and robustness by using the QueryFunctionContext (provides signal and queryKey) and guarding on a falsy characterId. This also avoids relying on a closure for id resolution.

Apply this diff:

-  const query = createQuery(() => ({
-    queryKey: ['character', characterId],
-    queryFn: getCharacter,
-  }))
+  const query = createQuery(() => ({
+    queryKey: ['character', characterId] as const,
+    enabled: !!characterId,
+    queryFn: async ({ signal, queryKey }) => {
+      const [, id] = queryKey as ['character', string]
+      const res = await fetch(`https://swapi.dev/api/people/${id}/`, { signal })
+      if (!res.ok) throw new Error(`Failed to fetch character ${id}`)
+      return res.json()
+    },
+  }))

Based on learnings


17-17: Prefer boolean flag over string comparison

Use the built-in boolean for readability and to avoid typos:

-{#if query.status === 'success'}
+{#if query.isSuccess}
packages/svelte-query-devtools/src/Devtools.svelte (1)

90-105: Guard setter calls with optional call chaining

If any setter isn’t present on the queried devtools version, current calls can throw at runtime. Use optional call chaining on the method.

Apply:

-    $effect(() => {
-      devtools?.setButtonPosition(buttonPosition)
-    })
+    $effect(() => {
+      devtools?.setButtonPosition?.(buttonPosition)
+    })
 
-    $effect(() => {
-      devtools?.setPosition(position)
-    })
+    $effect(() => {
+      devtools?.setPosition?.(position)
+    })
 
-    $effect(() => {
-      devtools?.setInitialIsOpen(initialIsOpen)
-    })
+    $effect(() => {
+      devtools?.setInitialIsOpen?.(initialIsOpen)
+    })
 
-    $effect(() => {
-      devtools?.setErrorTypes(errorTypes)
-    })
+    $effect(() => {
+      devtools?.setErrorTypes?.(errorTypes)
+    })
.changeset/pink-pots-jam.md (1)

7-7: Make the changeset note more actionable (add concrete migration pointers).

Consider briefly calling out key breaking changes and the new invocation pattern to reduce upgrade friction.

Apply this diff to enrich the note:

-BREAKING: Migrate to svelte runes (svelte v5+). Please see documentation for migration guide.
+BREAKING: Migrate to Svelte runes (Svelte v5+). See the migration guide in the docs.
+
+- Queries now use factory-wrapped options, e.g. `createQuery(() => ({ ... }))`, `createInfiniteQuery(() => ({ ... }))`.
+- Components adopt `$props()` and render children via `{@render children?.()}`.
+- Requires Svelte 5+ (ensure `compilerOptions.runes` is enabled).
examples/svelte/load-more-infinite-scroll/src/routes/+layout.svelte (1)

7-8: Guard children render to avoid runtime errors when no slot content.

If children are absent, calling children() throws. Use optional chaining.

Apply this diff:

-    {@render children()}
+    {@render children?.()}

Also applies to: 20-20

examples/svelte/star-wars/src/routes/+layout.svelte (1)

7-8: Use optional chaining when rendering children.

Prevents a crash if children are missing.

Apply this diff:

-  {@render children()}
+  {@render children?.()}

Also applies to: 30-30

examples/svelte/basic/src/routes/+layout.svelte (1)

9-10: Use optional chaining for resilient children render.

Avoids invoking an undefined render function.

Apply this diff:

-    {@render children()}
+    {@render children?.()}

Also applies to: 26-27

packages/svelte-query/tests/infiniteQueryOptions/infiniteQueryOptions.test-d.ts (1)

41-44: Fix stale comment reference.

The comment still mentions useInfiniteQuery.

Apply this diff:

-    // known issue: type of pageParams is unknown when returned from useInfiniteQuery
+    // known issue: type of pageParams is unknown when returned from createInfiniteQuery
examples/svelte/basic/src/routes/[postId]/+page.svelte (1)

4-4: Preserve types by annotating $props() extraction.

Without the prop export, data loses its PageData type. Add the type import and annotate the destructure.

Apply this diff:

-  const { data } = $props()
+  const { data }: { data: PageData } = $props()

Add this import at the top of the script block:

import type { PageData } from './$types'
examples/svelte/optimistic-updates/src/routes/+layout.svelte (1)

7-8: Optional-chain children render.

Prevents errors if no children are provided.

Apply this diff:

-    {@render children()}
+    {@render children?.()}

Also applies to: 20-20

examples/svelte/ssr/src/routes/[postId]/+page.svelte (1)

4-4: Optional: add type to props destructure for better DX.

Type the data prop inline to keep editor types without extra imports.

Apply:

-  const { data } = $props()
+  const { data }: { data: import('./$types').PageData } = $props()
examples/svelte/star-wars/src/routes/films/[filmId]/+page.svelte (1)

6-16: Inline queryFn with AbortSignal and capture id once.

Improves cancellation and avoids implicit dependency on page in a separate function.

Apply:

-  const getFilm = async () => {
-    const res = await fetch(
-      `https://swapi.dev/api/films/${page.params.filmId}/`,
-    )
-    return await res.json()
-  }
-
-  const query = createQuery(() => ({
-    queryKey: ['film', page.params.filmId],
-    queryFn: getFilm,
-  }))
+  const query = createQuery(() => {
+    const id = page.params.filmId
+    return {
+      queryKey: ['film', id],
+      queryFn: async ({ signal }) =>
+        fetch(`https://swapi.dev/api/films/${id}/`, { signal }).then((r) =>
+          r.json(),
+        ),
+    }
+  })
examples/svelte/simple/src/lib/Simple.svelte (1)

12-18: Use AbortSignal in queryFn to support cancellation.

Prevents in-flight fetches from completing after unmount/param change.

Apply:

-  const query = createQuery<Repo>(() => ({
+  const query = createQuery<Repo>(() => ({
     queryKey: ['repoData'],
-    queryFn: async () =>
-      await fetch('https://api.github.com/repos/TanStack/query').then((r) =>
-        r.json(),
-      ),
+    queryFn: async ({ signal }) =>
+      fetch('https://api.github.com/repos/TanStack/query', { signal }).then((r) =>
+        r.json(),
+      ),
   }))
examples/svelte/optimistic-updates/src/routes/+page.svelte (2)

39-43: Strengthen error typing or guard for message

todos.error defaults to unknown; accessing message may fail typing. Either annotate Error as the second generic or guard in template.

Option A (type-safe generics):

-const todos = createQuery<Todos>(() => ({
+const todos = createQuery<Todos, Error>(() => ({
  queryKey: ['optimistic'],
  queryFn: fetchTodos,
}))

Option B (template-safe guard):

-{#if todos.error}
-  An error has occurred:
-  {todos.error.message}
+{#if todos.error}
+  An error has occurred:
+  {todos.error instanceof Error ? todos.error.message : String(todos.error)}
{/if}

Also applies to: 102-108


44-78: Optional: add mutation generics for better type safety

You can tighten types for err/variables/context to remove any casts and improve DX.

Example:

type Ctx = { previousTodos?: Todos }
const addTodoMutation = createMutation<Todo, Error, string, Ctx>(() => ({
  // ...
  onError: (_err, _variables, context) => {
    if (context?.previousTodos) {
      client.setQueryData(['optimistic'], context.previousTodos)
    }
  },
}))
examples/svelte/load-more-infinite-scroll/src/lib/LoadMore.svelte (2)

26-31: Guard error message for unknown error types

query.error may not be an Error; guard to avoid type/runtime issues.

-{#if query.error}
-  <span>Error: {query.error.message}</span>
+{#if query.error}
+  <span>Error: {query.error instanceof Error ? query.error.message : String(query.error)}</span>
{/if}

50-55: Prefer isFetchingNextPage for the button label

Using isFetching can show “Loading more...” during other refetches. isFetchingNextPage better reflects the button action.

-      {#if query.isFetching}
+      {#if query.isFetchingNextPage}
         Loading more...
       {:else if query.hasNextPage}
         Load More
       {:else}Nothing more to load{/if}
packages/svelte-query/tests/utils.svelte.ts (1)

14-22: Tighten promiseWithResolvers types

Expose standard PromiseLike resolution and unknown for reject reason.

-export function promiseWithResolvers<T>() {
-  let resolve: (value: T) => void
-  let reject: (reason?: any) => void
+export function promiseWithResolvers<T>() {
+  let resolve!: (value: T | PromiseLike<T>) => void
+  let reject!: (reason?: unknown) => void
   const promise = new Promise<T>((res, rej) => {
     resolve = res
     reject = rej
   })
-  return { promise, resolve: resolve!, reject: reject! }
+  return { promise, resolve, reject }
 }
examples/svelte/basic/src/lib/Posts.svelte (1)

7-7: Use const for non-mutated value

limit is never reassigned; prefer const.

-let limit = 10
+const limit = 10
packages/svelte-query/src/createMutation.svelte.ts (2)

48-52: Batch updates with notifyManager.batch, not batchCalls invoked immediately

batchCalls returns a wrapper for callbacks; for inline updates, batch is the simpler/clearer choice.

Apply this diff:

-  const unsubscribe = observer.subscribe((val) => {
-    notifyManager.batchCalls(() => {
-      Object.assign(result, val)
-    })()
-  })
+  const unsubscribe = observer.subscribe((val) => {
+    notifyManager.batch(() => {
+      Object.assign(result, val)
+    })
+  })

60-69: Avoid spreading result on every get; it over‑tracks and allocates

Spreading result in the get trap reads all fields (over‑tracking) and allocates a new object for each property access. Return only the requested property.

Apply this diff:

-    get: (_, prop) => {
-      const r = {
-        ...result,
-        mutate,
-        mutateAsync: result.mutate,
-      }
-      if (prop == 'value') return r
-      // @ts-expect-error
-      return r[prop]
-    },
+    get: (_, prop) => {
+      if (prop === 'value') {
+        return { ...result, mutate, mutateAsync: result.mutate }
+      }
+      if (prop === 'mutate') return mutate
+      if (prop === 'mutateAsync') return result.mutate
+      // @ts-expect-error
+      return (result as any)[prop]
+    },
examples/svelte/ssr/src/lib/Post.svelte (1)

8-11: Guard the query with enabled to avoid fetching when postId is falsy

Prevents unnecessary fetches and aligns with the template’s conditional.

Apply this diff:

   const post = createQuery<Post>(() => ({
     queryKey: ['post', postId],
     queryFn: () => api().getPostById(postId),
+    enabled: !!postId,
   }))
packages/svelte-query-persist-client/src/PersistQueryClientProvider.svelte (4)

23-29: Avoid passing onSuccess/onError through to QueryClientProvider

Extract them explicitly so they aren’t spread into .

Apply this diff:

-  let {
-    client,
-    children,
-    persistOptions,
-    ...props
-  }: PersistQueryClientProviderProps = $props()
+  let {
+    client,
+    children,
+    persistOptions,
+    onSuccess,
+    onError,
+    ...restProps
+  }: PersistQueryClientProviderProps = $props()

39-41: Return undefined during restore instead of a noop cleanup

Slightly cleaner effect cleanup semantics.

Apply this diff:

-  $effect(() => {
-    return isRestoring.current ? () => {} : persistQueryClientSubscribe(options)
-  })
+  $effect(() =>
+    isRestoring.current ? undefined : persistQueryClientSubscribe(options),
+  )

46-47: Reference extracted callbacks directly

Avoid reading from the spread props bag.

Apply this diff:

-      .then(() => props.onSuccess?.())
-      .catch(() => props.onError?.())
+      .then(() => onSuccess?.())
+      .catch(() => onError?.())

54-56: Spread only remaining provider props

Prevents leaking unrelated props to the provider.

Apply this diff:

-<QueryClientProvider {client} {...props}>
+<QueryClientProvider {client} {...restProps}>
packages/svelte-query/tests/useIsMutating/BaseExample.svelte (1)

18-18: Use Svelte event directive on:click instead of onclick

onclick may not attach as an event handler in Svelte templates; on:click is the idiomatic directive.

Apply this diff:

-<button onclick={() => mutation.mutate()}>Trigger</button>
+<button on:click={() => mutation.mutate()}>Trigger</button>

If you intentionally rely on a Svelte 5 change that supports onclick, please confirm; otherwise switch to on:click.

packages/svelte-query/tests/createQuery.svelte.test.ts (1)

228-236: Avoid wrapping createQuery in $derived

$derived here would recreate the query instance when dependencies change. Use the query result directly.

Apply this diff:

-      const query = $derived(
-        createQuery<string, Error>(
-          () => ({
-            queryKey: key,
-            queryFn: () => promise,
-          }),
-          () => queryClient,
-        ),
-      )
+      const query = createQuery<string, Error>(
+        () => ({
+          queryKey: key,
+          queryFn: () => promise,
+        }),
+        () => queryClient,
+      )
packages/svelte-query/tests/createQueries.svelte.test.ts (3)

370-379: Avoid sparse arrays with Array(50).map in runtime paths.

Array(50).map produces a sparse array (map won’t run). Use Array.from to avoid accidental runtime surprises even in type-centric tests.

Apply:

-          queries: Array(50).map((_, i) => ({
+          queries: Array.from({ length: 50 }, (_, i) => ({
             queryKey: ['key', i] as const,
             queryFn: () => i + 10,
-          })),
+          })),

486-495: Same sparse array issue (with select).

Apply:

-          queries: Array(50).map((_, i) => ({
+          queries: Array.from({ length: 50 }, (_, i) => ({
             queryKey: ['key', i] as const,
             queryFn: () => i + 10,
             select: (data: number) => data.toString(),
-          })),
+          })),

583-593: Same sparse array issue (fetch/Promise).

Apply:

-          queries: Array(50).map((_, i) => ({
+          queries: Array.from({ length: 50 }, (_, i) => ({
             queryKey: ['key', i] as const,
             queryFn: () =>
               fetch('return Promise<any>').then((resp) => resp.json()),
-          })),
+          })),
examples/svelte/ssr/src/routes/[postId]/+page.ts (1)

7-7: Specify radix for parseInt.

Avoid implicit radix to prevent edge-case parsing.

Apply:

-  const postId = parseInt(params.postId)
+  const postId = parseInt(params.postId, 10)
examples/svelte/auto-refetching/src/routes/+layout.svelte (2)

7-8: Type the children prop for TS (Svelte 5 Snippet).

For better DX and to avoid implicit any, type children as Snippet.

 <script lang="ts">
+  import type { Snippet } from 'svelte'
   import '../app.css'
   import { browser } from '$app/environment'
   import { QueryClientProvider, QueryClient } from '@tanstack/svelte-query'
   import { SvelteQueryDevtools } from '@tanstack/svelte-query-devtools'

-  const { children } = $props()
+  const { children }: { children: Snippet } = $props()

18-23: Avoid SSR mismatch: render Devtools only in the browser.

SvelteQueryDevtools should be gated by browser to prevent SSR/CSR markup mismatch and unnecessary server work.

Suggested change:

<QueryClientProvider client={queryClient}>
  <main>
    {@render children()}
  </main>
  {#if browser}
    <SvelteQueryDevtools />
  {/if}
</QueryClientProvider>
examples/svelte/star-wars/src/routes/films/+page.svelte (1)

9-41: Rune migration looks good; consider adding a keyed each for stability.

  • The function-wrapped createQuery and direct query.* access align with the runes adapter.
  • Add a stable key to the each block to minimize DOM churn:
-    {#each query.data.results as film}
+    {#each query.data.results as film (film.url)}
packages/svelte-query/src/useIsRestoring.ts (1)

2-6: Type alignment check: Box shape vs context return.

Confirm getIsRestoringContext() returns a Box with the same shape you export (e.g., current vs value). If consumers expect isRestoring.current, returning a mismatched shape will break them. If there’s a possible “no provider” path, consider a safe default.

examples/svelte/playground/src/routes/+page.svelte (1)

32-32: Optional: ensure numeric coercion on inputs if needed.

bind:value on number inputs should yield numbers, but if any path treats them as strings, coerce explicitly (e.g., bind:value={staleTime.value} and validate downstream) to avoid accidental string math.

Also applies to: 42-42, 55-55, 65-65, 74-74

packages/svelte-query/tests/createInfiniteQuery/createInfiniteQuery.svelte.test.ts (1)

18-33: LGTM on ref migration; you can make states const.

You mutate states.value but don’t reassign states; prefer const for clarity:

-    let states = ref<Array<QueryObserverResult>>([])
+    const states = ref<Array<QueryObserverResult>>([])
examples/svelte/star-wars/src/routes/characters/+page.svelte (1)

9-36: Rune migration looks good; add keyed each for better diffing.

Same as films example, key the each block:

-    {#each query.data.results as person}
+    {#each query.data.results as person (person.url)}
packages/svelte-query/tests/QueryClientProvider/ParentComponent.svelte (1)

4-7: LGTM; optional ergonomic tweak for props typing.

You can type $props directly to avoid inline annotation:

-  let { queryClient }: { queryClient: QueryClient } = $props()
+  const { queryClient } = $props<{ queryClient: QueryClient }>()
packages/svelte-query/tests/createInfiniteQuery/BaseExample.svelte (1)

12-20: Rune migration looks correct; consider avoiding ts-ignore with a typed snapshot helper.

The new Accessor-based call and client factory are consistent. The $effect + untrack pattern is fine. To avoid ts-expect-error/svelte-ignore, you can cast the snapshot once via a helper.

Example:

+  const snapshot = () => $state.snapshot(query) as unknown as QueryObserverResult
  $effect(() => {
-    // @ts-expect-error
-    // svelte-ignore state_snapshot_uncloneable
-    states.value = [...untrack(() => states.value), $state.snapshot(query)]
+    states.value = [...untrack(() => states.value), snapshot()]
  })

Also applies to: 22-26

packages/svelte-query/src/useIsMutating.svelte.ts (2)

12-15: Batch cache notifications to reduce redundant recomputations.

Wrap the subscriber with notifyManager.batchCalls to coalesce updates (mirrors prior store-based patterns and React/Solid adapters).

Apply:

+import { notifyManager } from '@tanstack/query-core'
 
 export function useIsMutating(
   filters?: MutationFilters,
   queryClient?: QueryClient,
 ): ReactiveValue<number> {
   const client = useQueryClient(queryClient)
   const cache = client.getMutationCache()
 
   return new ReactiveValue(
     () => client.isMutating(filters),
-    (update) => cache.subscribe(update),
+    (update) => cache.subscribe(notifyManager.batchCalls(() => update())),
   )
 }

5-8: Non-reactive filters/client params.

Passing plain filters/queryClient means changes to those won’t trigger recomputation until the cache emits an event. If that’s intentional, fine; otherwise consider Accessor<MutationFilters | QueryClient> for reactive args (aligns with other Accessor-based APIs).

Can you confirm the intended semantics for filters reactivity here?

packages/svelte-query/src/useIsFetching.svelte.ts (2)

12-15: Batch query cache notifications.

Use notifyManager.batchCalls to avoid excessive updates during bursts.

Apply:

+import { notifyManager } from '@tanstack/query-core'
 
 export function useIsFetching(
   filters?: QueryFilters,
   queryClient?: QueryClient,
 ): ReactiveValue<number> {
   const client = useQueryClient(queryClient)
   const queryCache = client.getQueryCache()
 
   return new ReactiveValue(
     () => client.isFetching(filters),
-    (update) => queryCache.subscribe(update),
+    (update) => queryCache.subscribe(notifyManager.batchCalls(() => update())),
   )
 }

5-8: Filters/client are non-reactive.

Same note as useIsMutating: if consumers expect filters changes to reflect immediately (without a cache event), consider Accessor parameters.

Is this API intentionally non-reactive for filters?

packages/svelte-query/src/HydrationBoundary.svelte (1)

10-15: Tighten Props typing and defaults.

Prefer optional props over unions-with-undefined for options/queryClient for readability; keep required children/state.

Apply:

-  type Props = {
-    children: Snippet
-    state: DehydratedState
-    options: HydrateOptions | undefined
-    queryClient: QueryClient | undefined
-  }
+  type Props = {
+    children: Snippet
+    state: DehydratedState
+    options?: HydrateOptions
+    queryClient?: QueryClient
+  }
 
   const {
     children,
     state,
-    options = undefined,
-    queryClient = undefined,
+    options,
+    queryClient,
   }: Props = $props()

Also applies to: 17-22, 27-27

packages/svelte-query/tests/QueryClientProvider/QueryClientProvider.svelte.test.ts (1)

15-29: Clean up QueryClient after test to prevent cross-test contamination.

Clear the client to avoid retained queries between tests.

Apply:

   test('Sets a specific cache for all queries to use', async () => {
     const queryClient = new QueryClient()
     const queryCache = queryClient.getQueryCache()
@@
     expect(queryCache.find({ queryKey: ['hello'] })).toBeDefined()
+    queryClient.clear()
   })
packages/svelte-query/tests/createInfiniteQuery/SelectExample.svelte (1)

12-24: Consistent runes migration; typings nit optional.

The Accessor-based call and untrack + snapshot pattern are consistent. Optionally avoid ts-ignore by casting a snapshot helper as noted in BaseExample.

Also applies to: 26-30, 33-33

packages/svelte-query-persist-client/tests/RemoveCache/Provider.svelte (1)

7-14: Widen onSuccess/onError to support async handlers

 interface Props {
   queryClient: QueryClient
   persistOptions: OmitKeyof<PersistQueryClientOptions, 'queryClient'>
-  onSuccess: () => void
-  onError: () => void
+  onSuccess: () => void | Promise<void>
+  onError: () => void | Promise<void>
 }

QueryClient and OmitKeyof are correctly re-exported from @tanstack/svelte-query.

packages/svelte-query/src/useMutationState.svelte.ts (2)

1-1: Batch cache notifications to avoid update thrashing

Wrap the subscription callback with notifyManager.batchCalls like the store-based variant to coalesce updates.

Apply this diff:

-import { replaceEqualDeep } from '@tanstack/query-core'
+import { notifyManager, replaceEqualDeep } from '@tanstack/query-core'
@@
-  $effect(() => {
-    const unsubscribe = mutationCache.subscribe(() => {
+  $effect(() => {
+    const unsubscribe = mutationCache.subscribe(
+      notifyManager.batchCalls(() => {
         const nextResult = replaceEqualDeep(
           result,
           getResult(mutationCache, options),
         )
         if (result !== nextResult) {
-          Object.assign(result, nextResult)
+          result.splice(0, result.length, ...nextResult)
         }
-    })
+      }),
+    )

Based on learnings.

Also applies to: 30-38


43-54: Remove dead/commented block

The alternative $effect block is outdated (references result.current/optionsRef) and can be removed.

Apply this diff:

-  /*  $effect(() => {
-    mutationCache.subscribe(() => {
-      const nextResult = replaceEqualDeep(
-        result.current,
-        getResult(mutationCache, optionsRef),
-      )
-      if (result.current !== nextResult) {
-        result = nextResult
-        //notifyManager.schedule(onStoreChange)
-      }
-    })
-  }) */
packages/svelte-query/src/context.ts (1)

27-35: Optional: return a stable default Box instance

Returning a new { current: false } each call can defeat identity checks. A module-level singleton default avoids churn.

Apply this diff:

+const _defaultIsRestoring: Box<boolean> = { current: false }
@@
-    return isRestoring ?? { current: false }
+    return isRestoring ?? _defaultIsRestoring
   } catch (error) {
-    return { current: false }
+    return _defaultIsRestoring
   }
packages/svelte-query-persist-client/tests/AwaitOnSuccess/Provider.svelte (1)

6-6: Align import path extension for NodeNext/ESM consistency

Other files import from utils.svelte.js. Consider using the .js extension here to avoid resolution issues in strict ESM setups.

Apply this diff:

-import { StatelessRef } from '../utils.svelte'
+import { StatelessRef } from '../utils.svelte.js'

If your tooling resolves extensionless paths reliably, feel free to keep as-is.

examples/svelte/playground/src/routes/TodosList.svelte (1)

33-36: Minor: use object shorthand in options

Small readability tweak; no behavior change.

-  const query = createQuery(() => ({
-    queryKey: ['todos', { filter: filter }],
-    queryFn: () => fetchTodos({ filter: filter }),
-  }))
+  const query = createQuery(() => ({
+    queryKey: ['todos', { filter }],
+    queryFn: () => fetchTodos({ filter }),
+  }))
packages/svelte-query/tests/containers.svelte.test.ts (1)

1-219: Solid coverage of proxy traps and reactivity

Great suite validating set/has/ownKeys/getOwnPropertyDescriptor traps, lazy access, arrays, and effect behavior.

Consider adding:

  • A test for symbol keys in objects (ensuring ownKeys filtering works with PropertyKey beyond strings).
  • An array test deleting a non-terminal index (e.g., delete ref[1]) to confirm length handling and visibility are as intended.
packages/svelte-query/src/createBaseQuery.svelte.ts (1)

60-66: Consider memory leak prevention for subscriptions

The effect correctly returns an unsubscribe function, but there's a potential issue: if isRestoring.current changes from true to false while the effect is running, the subscription won't be created until the next effect run.

Consider restructuring to handle the subscription more robustly:

  $effect(() => {
-    const unsubscribe = isRestoring.current
-      ? () => undefined
-      : observer.subscribe(() => update(createResult()))
-    observer.updateResult()
-    return unsubscribe
+    let unsubscribe: (() => void) | undefined
+    
+    if (!isRestoring.current) {
+      unsubscribe = observer.subscribe(() => update(createResult()))
+    }
+    observer.updateResult()
+    
+    return () => unsubscribe?.()
  })
examples/svelte/auto-refetching/src/routes/+page.svelte (2)

63-69: Prefer modern event handler syntax

While onsubmit works, consider using Svelte 5's event handler syntax for consistency.

-  onsubmit={(e) => {
+  onsubmit={(e: SubmitEvent) => {

88-88: Missing type safety for mutation parameter

The undefined parameter to clearMutation.mutate could be more explicit about intent.

Consider adding a comment or using void to make the intent clearer:

-    <button onclick={() => clearMutation.mutate(undefined)}> Clear All </button>
+    <button onclick={() => clearMutation.mutate(undefined /* no parameters required */)}> Clear All </button>
packages/svelte-query-persist-client/tests/RestoreCache/RestoreCache.svelte (1)

13-17: Unbounded state accumulation in tests

$effect pushes a new snapshot on every reactive update and never clears it. If the test expects only specific lifecycle points, consider constraining or cleaning up to avoid unbounded growth.

Example options:

  • Guard to push only when fetchStatus/data change meaningfully.
  • Add cleanup to reset states on destroy.
 $effect(() => {
   // svelte-ignore state_snapshot_uncloneable
   const snapshot = $state.snapshot(query)
   states.current.push(snapshot)
+  return () => {
+    // keep array instance but clear entries to avoid leaks across mounts
+    states.current.length = 0
+  }
 })
examples/svelte/playground/src/lib/stores.svelte.ts (1)

1-12: Type the setter parameter for better inference

Annotate newValue so ref preserves types in setters.

 export function ref<T>(initial: T) {
   let value = $state(initial)

   return {
     get value() {
       return value
     },
-    set value(newValue) {
+    set value(newValue: T) {
       value = newValue
     },
   }
 }
packages/svelte-query-persist-client/tests/FreshData/Provider.svelte (1)

8-14: Validate availability of OmitKeyof

OmitKeyof may not be exported by @tanstack/svelte-query. If it isn’t, switch to the built-in Omit utility type.

-  import type { OmitKeyof, QueryClient } from '@tanstack/svelte-query'
+  import type { QueryClient } from '@tanstack/svelte-query'
   import type { PersistQueryClientOptions } from '@tanstack/query-persist-client-core'
 ...
-    persistOptions: OmitKeyof<PersistQueryClientOptions, 'queryClient'>
+    persistOptions: Omit<PersistQueryClientOptions, 'queryClient'>
examples/svelte/playground/src/routes/EditTodo.svelte (1)

18-39: Minor: order existence check before snapshot

Avoid snapshotting a possibly undefined value; check first, then snapshot to keep intent clear.

-          const todo = $state.snapshot(list.value.find((d) => d.id === id))
-          if (!todo) {
+          const found = list.value.find((d) => d.id === id)
+          if (!found) {
             return reject(
               new Error(JSON.stringify({ fetchTodoById: { id } }, null, 2)),
             )
           }
-          resolve(todo)
+          resolve($state.snapshot(found))
packages/svelte-query/src/containers.svelte.ts (2)

87-103: Align array length to new value in update()

After removing/setting keys, explicitly set the array length to match the new array to avoid stale trailing entries.

   function update(newValue: T) {
     const existingKeys = Object.keys(out)
     const newKeys = Object.keys(newValue)
     const keysToRemove = existingKeys.filter((key) => !newKeys.includes(key))
     for (const key of keysToRemove) {
       // @ts-expect-error
       delete out[key]
     }
     for (const key of newKeys) {
       // @ts-expect-error
       // This craziness is required because Tanstack Query defines getters for all of the keys on the object.
       // These getters track property access, so if we access all of them here, we'll end up tracking everything.
       // So we wrap the property access in a special function that we can identify later to lazily access the value.
       // (See above)
       out[key] = brand(() => newValue[key])
     }
+    if (Array.isArray(out) && Array.isArray(newValue)) {
+      // ensure visible length matches new array size
+      // @ts-expect-error
+      out.length = newValue.length
+    }
   }

Also applies to: 105-110


41-53: Preserve reactivity on direct assignments

When assigning to existing keys, the setter replaces state with a non-reactive value. Wrap non-branded values in $state.raw so future reads stay reactive; keep branded values as-is to retain laziness.

       Object.defineProperty(target, prop, {
         configurable: true,
         enumerable: true,
         get: () => {
           // If this is a lazy value, we need to call it.
           // We can't do something like typeof state === 'function'
           // because the value could actually be a function that we don't want to call.
           return state && isBranded(state) ? state() : state
         },
-        set: (v) => {
-          state = v
-        },
+        set: (v) => {
+          // keep laziness if branded; otherwise make it reactive
+          // @ts-expect-error
+          state = isBranded(v) ? v : $state.raw(v)
+        },
       })

Please re-run the affected containers tests to ensure this doesn’t inadvertently over-track properties (the branding path should still avoid eager access). Based on learnings

examples/svelte/star-wars/src/routes/characters/[characterId]/Film.svelte (1)

17-23: Prefer boolean flags over status string checks

For readability and resilience to status value changes, consider isSuccess.

Apply this diff:

-{#if query.status === 'success'}
+{#if query.isSuccess}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8e42926 and e3bba4a.

📒 Files selected for processing (131)
  • .changeset/pink-pots-jam.md (1 hunks)
  • docs/config.json (0 hunks)
  • docs/framework/svelte/installation.md (0 hunks)
  • docs/framework/svelte/overview.md (3 hunks)
  • docs/framework/svelte/reactivity.md (0 hunks)
  • docs/framework/svelte/ssr.md (2 hunks)
  • eslint.config.js (1 hunks)
  • examples/svelte/auto-refetching/src/routes/+layout.svelte (2 hunks)
  • examples/svelte/auto-refetching/src/routes/+page.svelte (2 hunks)
  • examples/svelte/auto-refetching/svelte.config.js (1 hunks)
  • examples/svelte/basic/src/lib/Post.svelte (1 hunks)
  • examples/svelte/basic/src/lib/Posts.svelte (3 hunks)
  • examples/svelte/basic/src/routes/+layout.svelte (2 hunks)
  • examples/svelte/basic/src/routes/[postId]/+page.svelte (1 hunks)
  • examples/svelte/basic/svelte.config.js (1 hunks)
  • examples/svelte/load-more-infinite-scroll/src/app.css (3 hunks)
  • examples/svelte/load-more-infinite-scroll/src/lib/LoadMore.svelte (4 hunks)
  • examples/svelte/load-more-infinite-scroll/src/routes/+layout.svelte (2 hunks)
  • examples/svelte/load-more-infinite-scroll/svelte.config.js (1 hunks)
  • examples/svelte/optimistic-updates/src/routes/+layout.svelte (2 hunks)
  • examples/svelte/optimistic-updates/src/routes/+page.svelte (4 hunks)
  • examples/svelte/optimistic-updates/svelte.config.js (1 hunks)
  • examples/svelte/playground/src/lib/stores.svelte.ts (1 hunks)
  • examples/svelte/playground/src/lib/stores.ts (0 hunks)
  • examples/svelte/playground/src/routes/+layout.svelte (2 hunks)
  • examples/svelte/playground/src/routes/+page.svelte (6 hunks)
  • examples/svelte/playground/src/routes/AddTodo.svelte (1 hunks)
  • examples/svelte/playground/src/routes/App.svelte (1 hunks)
  • examples/svelte/playground/src/routes/EditTodo.svelte (4 hunks)
  • examples/svelte/playground/src/routes/TodosList.svelte (1 hunks)
  • examples/svelte/playground/svelte.config.js (1 hunks)
  • examples/svelte/simple/src/lib/Simple.svelte (1 hunks)
  • examples/svelte/simple/src/main.ts (1 hunks)
  • examples/svelte/simple/svelte.config.js (1 hunks)
  • examples/svelte/ssr/src/lib/Post.svelte (1 hunks)
  • examples/svelte/ssr/src/lib/Posts.svelte (3 hunks)
  • examples/svelte/ssr/src/routes/+layout.svelte (1 hunks)
  • examples/svelte/ssr/src/routes/+layout.ts (1 hunks)
  • examples/svelte/ssr/src/routes/+page.ts (1 hunks)
  • examples/svelte/ssr/src/routes/[postId]/+page.svelte (1 hunks)
  • examples/svelte/ssr/src/routes/[postId]/+page.ts (1 hunks)
  • examples/svelte/ssr/svelte.config.js (1 hunks)
  • examples/svelte/star-wars/src/routes/+layout.svelte (2 hunks)
  • examples/svelte/star-wars/src/routes/characters/+page.svelte (1 hunks)
  • examples/svelte/star-wars/src/routes/characters/[characterId]/+page.svelte (3 hunks)
  • examples/svelte/star-wars/src/routes/characters/[characterId]/+page.ts (0 hunks)
  • examples/svelte/star-wars/src/routes/characters/[characterId]/Film.svelte (1 hunks)
  • examples/svelte/star-wars/src/routes/characters/[characterId]/Homeworld.svelte (1 hunks)
  • examples/svelte/star-wars/src/routes/films/+page.svelte (1 hunks)
  • examples/svelte/star-wars/src/routes/films/[filmId]/+page.svelte (1 hunks)
  • examples/svelte/star-wars/src/routes/films/[filmId]/+page.ts (0 hunks)
  • examples/svelte/star-wars/src/routes/films/[filmId]/Character.svelte (1 hunks)
  • examples/svelte/star-wars/svelte.config.js (1 hunks)
  • packages/svelte-query-devtools/package.json (1 hunks)
  • packages/svelte-query-devtools/src/Devtools.svelte (2 hunks)
  • packages/svelte-query-persist-client/eslint.config.js (1 hunks)
  • packages/svelte-query-persist-client/package.json (1 hunks)
  • packages/svelte-query-persist-client/src/PersistQueryClientProvider.svelte (1 hunks)
  • packages/svelte-query-persist-client/src/utils.svelte.ts (1 hunks)
  • packages/svelte-query-persist-client/tests/AwaitOnSuccess/AwaitOnSuccess.svelte (1 hunks)
  • packages/svelte-query-persist-client/tests/AwaitOnSuccess/Provider.svelte (1 hunks)
  • packages/svelte-query-persist-client/tests/FreshData/FreshData.svelte (1 hunks)
  • packages/svelte-query-persist-client/tests/FreshData/Provider.svelte (1 hunks)
  • packages/svelte-query-persist-client/tests/InitialData/InitialData.svelte (2 hunks)
  • packages/svelte-query-persist-client/tests/InitialData/Provider.svelte (1 hunks)
  • packages/svelte-query-persist-client/tests/OnSuccess/OnSuccess.svelte (1 hunks)
  • packages/svelte-query-persist-client/tests/OnSuccess/Provider.svelte (1 hunks)
  • packages/svelte-query-persist-client/tests/PersistQueryClientProvider.svelte.test.ts (10 hunks)
  • packages/svelte-query-persist-client/tests/RemoveCache/Provider.svelte (1 hunks)
  • packages/svelte-query-persist-client/tests/RemoveCache/RemoveCache.svelte (1 hunks)
  • packages/svelte-query-persist-client/tests/RestoreCache/Provider.svelte (1 hunks)
  • packages/svelte-query-persist-client/tests/RestoreCache/RestoreCache.svelte (1 hunks)
  • packages/svelte-query-persist-client/tests/UseQueries/Provider.svelte (1 hunks)
  • packages/svelte-query-persist-client/tests/UseQueries/UseQueries.svelte (1 hunks)
  • packages/svelte-query-persist-client/tests/utils.svelte.ts (1 hunks)
  • packages/svelte-query-persist-client/vite.config.ts (0 hunks)
  • packages/svelte-query/eslint.config.js (1 hunks)
  • packages/svelte-query/package.json (2 hunks)
  • packages/svelte-query/src/HydrationBoundary.svelte (1 hunks)
  • packages/svelte-query/src/QueryClientProvider.svelte (2 hunks)
  • packages/svelte-query/src/containers.svelte.ts (1 hunks)
  • packages/svelte-query/src/context.ts (1 hunks)
  • packages/svelte-query/src/createBaseQuery.svelte.ts (1 hunks)
  • packages/svelte-query/src/createBaseQuery.ts (0 hunks)
  • packages/svelte-query/src/createInfiniteQuery.ts (4 hunks)
  • packages/svelte-query/src/createMutation.svelte.ts (1 hunks)
  • packages/svelte-query/src/createMutation.ts (0 hunks)
  • packages/svelte-query/src/createQueries.svelte.ts (7 hunks)
  • packages/svelte-query/src/createQuery.ts (2 hunks)
  • packages/svelte-query/src/index.ts (1 hunks)
  • packages/svelte-query/src/types.ts (6 hunks)
  • packages/svelte-query/src/useIsFetching.svelte.ts (1 hunks)
  • packages/svelte-query/src/useIsFetching.ts (0 hunks)
  • packages/svelte-query/src/useIsMutating.svelte.ts (1 hunks)
  • packages/svelte-query/src/useIsMutating.ts (0 hunks)
  • packages/svelte-query/src/useIsRestoring.ts (1 hunks)
  • packages/svelte-query/src/useMutationState.svelte.ts (1 hunks)
  • packages/svelte-query/src/useMutationState.ts (0 hunks)
  • packages/svelte-query/src/utils.ts (0 hunks)
  • packages/svelte-query/tests/QueryClientProvider/ChildComponent.svelte (1 hunks)
  • packages/svelte-query/tests/QueryClientProvider/ParentComponent.svelte (1 hunks)
  • packages/svelte-query/tests/QueryClientProvider/QueryClientProvider.svelte.test.ts (2 hunks)
  • packages/svelte-query/tests/containers.svelte.test.ts (1 hunks)
  • packages/svelte-query/tests/createInfiniteQuery/BaseExample.svelte (1 hunks)
  • packages/svelte-query/tests/createInfiniteQuery/SelectExample.svelte (2 hunks)
  • packages/svelte-query/tests/createInfiniteQuery/createInfiniteQuery.svelte.test.ts (4 hunks)
  • packages/svelte-query/tests/createMutation/FailureExample.svelte (1 hunks)
  • packages/svelte-query/tests/createMutation/OnSuccessExample.svelte (1 hunks)
  • packages/svelte-query/tests/createMutation/ResetExample.svelte (1 hunks)
  • packages/svelte-query/tests/createQueries.svelte.test.ts (1 hunks)
  • packages/svelte-query/tests/createQueries.test-d.ts (1 hunks)
  • packages/svelte-query/tests/createQueries/BaseExample.svelte (0 hunks)
  • packages/svelte-query/tests/createQueries/CombineExample.svelte (0 hunks)
  • packages/svelte-query/tests/createQueries/createQueries.test-d.ts (0 hunks)
  • packages/svelte-query/tests/createQueries/createQueries.test.ts (0 hunks)
  • packages/svelte-query/tests/createQuery.svelte.test.ts (1 hunks)
  • packages/svelte-query/tests/createQuery.test-d.ts (1 hunks)
  • packages/svelte-query/tests/createQuery/BaseExample.svelte (0 hunks)
  • packages/svelte-query/tests/createQuery/DisabledExample.svelte (0 hunks)
  • packages/svelte-query/tests/createQuery/PlaceholderData.svelte (0 hunks)
  • packages/svelte-query/tests/createQuery/RefetchExample.svelte (0 hunks)
  • packages/svelte-query/tests/createQuery/createQuery.test-d.ts (0 hunks)
  • packages/svelte-query/tests/createQuery/createQuery.test.ts (0 hunks)
  • packages/svelte-query/tests/infiniteQueryOptions/infiniteQueryOptions.test-d.ts (1 hunks)
  • packages/svelte-query/tests/queryOptions/queryOptions.test-d.ts (1 hunks)
  • packages/svelte-query/tests/useIsFetching/BaseExample.svelte (1 hunks)
  • packages/svelte-query/tests/useIsMutating/BaseExample.svelte (1 hunks)
  • packages/svelte-query/tests/useMutationState/BaseExample.svelte (2 hunks)
  • packages/svelte-query/tests/useMutationState/useMutationState.svelte.test.ts (3 hunks)
  • packages/svelte-query/tests/utils.svelte.ts (1 hunks)
  • packages/svelte-query/vite.config.ts (1 hunks)
💤 Files with no reviewable changes (23)
  • packages/svelte-query/tests/createQueries/createQueries.test.ts
  • packages/svelte-query/src/useMutationState.ts
  • packages/svelte-query/src/useIsMutating.ts
  • packages/svelte-query/tests/createQueries/CombineExample.svelte
  • examples/svelte/star-wars/src/routes/characters/[characterId]/+page.ts
  • examples/svelte/star-wars/src/routes/films/[filmId]/+page.ts
  • packages/svelte-query/tests/createQuery/createQuery.test.ts
  • packages/svelte-query/tests/createQueries/BaseExample.svelte
  • docs/framework/svelte/reactivity.md
  • packages/svelte-query/tests/createQuery/createQuery.test-d.ts
  • packages/svelte-query/src/createBaseQuery.ts
  • examples/svelte/playground/src/lib/stores.ts
  • packages/svelte-query/tests/createQuery/RefetchExample.svelte
  • packages/svelte-query-persist-client/vite.config.ts
  • packages/svelte-query/tests/createQuery/DisabledExample.svelte
  • packages/svelte-query/src/createMutation.ts
  • docs/config.json
  • packages/svelte-query/tests/createQuery/PlaceholderData.svelte
  • packages/svelte-query/src/utils.ts
  • packages/svelte-query/tests/createQuery/BaseExample.svelte
  • packages/svelte-query/src/useIsFetching.ts
  • packages/svelte-query/tests/createQueries/createQueries.test-d.ts
  • docs/framework/svelte/installation.md
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-09-02T17:57:33.184Z
Learnt from: TkDodo
PR: TanStack/query#9612
File: packages/query-async-storage-persister/src/asyncThrottle.ts:0-0
Timestamp: 2025-09-02T17:57:33.184Z
Learning: When importing from tanstack/query-core in other TanStack Query packages like query-async-storage-persister, a workspace dependency "tanstack/query-core": "workspace:*" needs to be added to the package.json.

Applied to files:

  • packages/svelte-query-persist-client/package.json
  • packages/svelte-query/package.json
  • packages/svelte-query-devtools/package.json
📚 Learning: 2025-08-19T03:18:18.303Z
Learnt from: oscartbeaumont
PR: TanStack/query#9564
File: packages/solid-query-devtools/src/production.tsx:2-3
Timestamp: 2025-08-19T03:18:18.303Z
Learning: In the solid-query-devtools package, the codebase uses a pattern of type-only default imports combined with typeof for component type annotations (e.g., `import type SolidQueryDevtoolsComp from './devtools'` followed by `typeof SolidQueryDevtoolsComp`). This pattern is consistently used across index.tsx and production.tsx files, and the maintainers prefer consistency over changing this approach.

Applied to files:

  • packages/svelte-query-devtools/src/Devtools.svelte
  • packages/svelte-query/src/index.ts
🧬 Code graph analysis (23)
packages/svelte-query-persist-client/src/utils.svelte.ts (1)
packages/svelte-query/src/containers.svelte.ts (2)
  • Box (6-6)
  • current (17-20)
packages/svelte-query/src/useIsMutating.svelte.ts (2)
packages/svelte-query/src/containers.svelte.ts (1)
  • ReactiveValue (8-21)
packages/solid-query/src/useIsMutating.ts (1)
  • useIsMutating (7-25)
packages/svelte-query/tests/queryOptions/queryOptions.test-d.ts (1)
packages/svelte-query/src/createQueries.svelte.ts (1)
  • createQueries (190-254)
packages/svelte-query/tests/createQueries.test-d.ts (2)
packages/svelte-query/src/createQueries.svelte.ts (1)
  • createQueries (190-254)
packages/svelte-query/src/types.ts (1)
  • CreateQueryResult (47-50)
packages/svelte-query/src/useIsFetching.svelte.ts (2)
packages/svelte-query/src/containers.svelte.ts (1)
  • ReactiveValue (8-21)
packages/svelte-query/src/useIsFetching.ts (1)
  • useIsFetching (10-33)
packages/svelte-query/src/useMutationState.svelte.ts (1)
packages/svelte-query/src/useMutationState.ts (1)
  • useMutationState (24-49)
packages/svelte-query/src/createBaseQuery.svelte.ts (4)
packages/query-core/src/types.ts (1)
  • QueryKey (53-61)
packages/svelte-query/src/types.ts (3)
  • Accessor (21-21)
  • CreateBaseQueryOptions (24-30)
  • CreateBaseQueryResult (33-36)
packages/svelte-query/src/useIsRestoring.ts (1)
  • useIsRestoring (4-6)
packages/svelte-query/src/containers.svelte.ts (1)
  • createRawRef (29-110)
packages/svelte-query/tests/containers.svelte.test.ts (2)
packages/svelte-query/tests/utils.svelte.ts (2)
  • ref (1-12)
  • withEffectRoot (24-33)
packages/svelte-query/src/containers.svelte.ts (1)
  • createRawRef (29-110)
packages/svelte-query/tests/createQueries.svelte.test.ts (4)
packages/svelte-query/tests/utils.svelte.ts (2)
  • withEffectRoot (24-33)
  • promiseWithResolvers (14-22)
packages/svelte-query/src/types.ts (2)
  • CreateQueryResult (47-50)
  • CreateQueryOptions (39-44)
packages/svelte-query/src/createQueries.svelte.ts (1)
  • createQueries (190-254)
packages/query-core/src/types.ts (3)
  • QueryFunction (96-100)
  • QueryKey (53-61)
  • QueryFunctionContext (138-165)
packages/svelte-query/src/createInfiniteQuery.ts (1)
packages/svelte-query/src/types.ts (1)
  • Accessor (21-21)
packages/svelte-query/tests/createQuery.test-d.ts (1)
packages/svelte-query/src/createQuery.ts (1)
  • createQuery (49-54)
packages/svelte-query-persist-client/tests/utils.svelte.ts (1)
packages/svelte-query/tests/utils.svelte.ts (2)
  • value (5-7)
  • value (8-10)
packages/svelte-query/tests/infiniteQueryOptions/infiniteQueryOptions.test-d.ts (1)
packages/svelte-query/src/createInfiniteQuery.ts (1)
  • createInfiniteQuery (16-39)
packages/svelte-query/tests/createInfiniteQuery/createInfiniteQuery.svelte.test.ts (2)
packages/svelte-query/tests/utils.svelte.ts (1)
  • ref (1-12)
packages/query-core/src/types.ts (1)
  • QueryObserverResult (899-904)
packages/svelte-query/tests/createQuery.svelte.test.ts (5)
packages/svelte-query/tests/utils.svelte.ts (2)
  • withEffectRoot (24-33)
  • promiseWithResolvers (14-22)
packages/svelte-query/src/createQuery.ts (1)
  • createQuery (49-54)
packages/query-core/src/query.ts (1)
  • promise (198-200)
packages/svelte-query/src/types.ts (1)
  • CreateQueryResult (47-50)
packages/query-core/src/queryObserver.ts (1)
  • refetch (296-302)
packages/svelte-query/src/useIsRestoring.ts (2)
packages/svelte-query/src/index.ts (1)
  • useIsRestoring (25-25)
packages/svelte-query/src/containers.svelte.ts (1)
  • Box (6-6)
examples/svelte/simple/src/main.ts (1)
integrations/svelte-vite/src/main.js (1)
  • app (3-5)
packages/svelte-query/src/createQueries.svelte.ts (4)
packages/query-core/src/types.ts (4)
  • DefaultError (47-51)
  • QueryKey (53-61)
  • QueryFunction (96-100)
  • ThrowOnError (300-310)
packages/svelte-query/src/types.ts (4)
  • CreateQueryOptions (39-44)
  • CreateQueryResult (47-50)
  • DefinedCreateQueryResult (80-83)
  • Accessor (21-21)
packages/svelte-query/src/useIsRestoring.ts (1)
  • useIsRestoring (4-6)
packages/svelte-query/src/containers.svelte.ts (1)
  • createRawRef (29-110)
packages/svelte-query/src/context.ts (1)
packages/svelte-query/src/containers.svelte.ts (1)
  • Box (6-6)
packages/svelte-query/src/types.ts (3)
packages/query-core/src/types.ts (4)
  • QueryObserverResult (899-904)
  • InfiniteQueryObserverResult (1060-1068)
  • DefaultError (47-51)
  • DefinedQueryObserverResult (892-897)
packages/angular-query-experimental/src/types.ts (1)
  • CreateBaseMutationResult (160-175)
packages/query-core/src/queryClient.ts (1)
  • QueryClient (61-648)
packages/svelte-query-persist-client/tests/PersistQueryClientProvider.svelte.test.ts (1)
packages/svelte-query-persist-client/tests/utils.svelte.ts (2)
  • StatelessRef (7-12)
  • StatusResult (1-5)
packages/svelte-query/src/createMutation.svelte.ts (3)
packages/query-core/src/types.ts (1)
  • DefaultError (47-51)
packages/svelte-query/src/types.ts (4)
  • Accessor (21-21)
  • CreateMutationOptions (86-94)
  • CreateMutationResult (132-137)
  • CreateMutateFunction (96-105)
packages/query-core/src/mutationObserver.ts (1)
  • mutate (128-143)
packages/svelte-query/src/createQuery.ts (3)
packages/svelte-query/src/types.ts (4)
  • Accessor (21-21)
  • CreateQueryResult (47-50)
  • DefinedCreateQueryResult (80-83)
  • CreateQueryOptions (39-44)
packages/svelte-query/src/queryOptions.ts (2)
  • UndefinedInitialDataOptions (10-17)
  • DefinedInitialDataOptions (19-28)
packages/query-core/src/types.ts (2)
  • DefaultError (47-51)
  • QueryKey (53-61)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Preview
  • GitHub Check: Test
  • GitHub Check: Test

Comment on lines +8 to +11
const post = createQuery<Post>(() => ({
queryKey: ['post', postId],
queryFn: () => getPostById(postId),
})
}))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Guard query execution when postId is falsy.

Without enabled, queryFn may run with undefined postId.

Apply:

-  const post = createQuery<Post>(() => ({
+  const post = createQuery<Post>(() => ({
     queryKey: ['post', postId],
     queryFn: () => getPostById(postId),
+    enabled: !!postId,
   }))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const post = createQuery<Post>(() => ({
queryKey: ['post', postId],
queryFn: () => getPostById(postId),
})
}))
const post = createQuery<Post>(() => ({
queryKey: ['post', postId],
queryFn: () => getPostById(postId),
enabled: !!postId,
}))
🤖 Prompt for AI Agents
In examples/svelte/basic/src/lib/Post.svelte around lines 8 to 11, the query is
created without guarding against a falsy postId so queryFn may run with
undefined; add an enabled flag such as enabled: Boolean(postId) (or !!postId) to
the createQuery options so the query will not execute until postId is truthy,
and keep the existing queryKey/queryFn but ensure getPostById is only called
when enabled.

Comment on lines +54 to 56
onclick={() => addMutation.mutate({ name, notes: name })}
disabled={addMutation.status === 'pending' || !name}
>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Use Svelte event directive for mutate action

Replace onclick with on:click to correctly register the handler.

-  <button
-    onclick={() => addMutation.mutate({ name, notes: name })}
-    disabled={addMutation.status === 'pending' || !name}
-  >
+  <button
+    on:click={() => addMutation.mutate({ name, notes: name })}
+    disabled={addMutation.status === 'pending' || !name}
+  >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
onclick={() => addMutation.mutate({ name, notes: name })}
disabled={addMutation.status === 'pending' || !name}
>
<button
on:click={() => addMutation.mutate({ name, notes: name })}
disabled={addMutation.status === 'pending' || !name}
>
🤖 Prompt for AI Agents
In examples/svelte/playground/src/routes/AddTodo.svelte around lines 54 to 56,
the button uses an HTML attribute `onclick` which Svelte won't register
correctly; replace `onclick={() => addMutation.mutate({ name, notes: name })}`
with the Svelte event directive `on:click={() => addMutation.mutate({ name,
notes: name })}` and keep the existing disabled binding
(`disabled={addMutation.status === 'pending' || !name}`) unchanged so the click
handler is properly wired in Svelte.

Comment on lines 219 to 225
const observer = $derived(
new QueriesObserver<TCombinedResult>(
client,
untrack(() => resolvedQueryOptions),
untrack(() => combine as QueriesObserverOptions<TCombinedResult>),
),
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix: pass an options object to QueriesObserver (not the combine function).

The third argument of QueriesObserver must be an options object (e.g., { combine }). Passing the combine function directly is incorrect and can lead to the observer missing its combine option at construction time.

Apply:

-  const observer = $derived(
-    new QueriesObserver<TCombinedResult>(
-      client,
-      untrack(() => resolvedQueryOptions),
-      untrack(() => combine as QueriesObserverOptions<TCombinedResult>),
-    ),
-  )
+  const observer = $derived(
+    new QueriesObserver<TCombinedResult>(
+      client,
+      untrack(() => resolvedQueryOptions),
+      untrack(
+        () => ({ combine } as QueriesObserverOptions<TCombinedResult>),
+      ),
+    ),
+  )

Based on learnings (QueriesObserver API expects an options object).

🤖 Prompt for AI Agents
In packages/svelte-query/src/createQueries.svelte.ts around lines 219 to 225,
the QueriesObserver is being constructed with the combine function as the third
argument instead of an options object; change the third argument to an object
with the combine property (e.g., { combine }) so the observer receives its
options correctly, using the same untrack wrapper as the other args and keeping
the QueriesObserverOptions<TCombinedResult> typing.

Comment on lines +21 to 47
options: Accessor<
UndefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>
>,
queryClient?: QueryClient,
): DefinedCreateQueryResult<TData, TError>
queryClient?: Accessor<QueryClient>,
): CreateQueryResult<TData, TError>

export function createQuery<
TQueryFnData = unknown,
TError = DefaultError,
TData = TQueryFnData,
TQueryKey extends QueryKey = QueryKey,
>(
options: StoreOrVal<
UndefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>
options: Accessor<
DefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>
>,
queryClient?: QueryClient,
): CreateQueryResult<TData, TError>
queryClient?: Accessor<QueryClient>,
): DefinedCreateQueryResult<TData, TError>

export function createQuery<
TQueryFnData = unknown,
TQueryFnData,
TError = DefaultError,
TData = TQueryFnData,
TQueryKey extends QueryKey = QueryKey,
>(
options: StoreOrVal<
CreateQueryOptions<TQueryFnData, TError, TData, TQueryKey>
>,
queryClient?: QueryClient,
options: Accessor<CreateQueryOptions<TQueryFnData, TError, TData, TQueryKey>>,
queryClient?: Accessor<QueryClient>,
): CreateQueryResult<TData, TError>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Re‑add the default type for TQueryFnData.

In the third overload we dropped = unknown from TQueryFnData. That forces callers who only want to specialize later generics (TError, TData, etc.) to supply dummy arguments, whereas the previous signature let them rely on the default and override downstream types. Please reinstate the default to avoid breaking existing call sites.

-export function createQuery<
-  TQueryFnData,
+export function createQuery<
+  TQueryFnData = unknown,
   TError = DefaultError,
   TData = TQueryFnData,
   TQueryKey extends QueryKey = QueryKey,
 >(
   options: Accessor<CreateQueryOptions<TQueryFnData, TError, TData, TQueryKey>>,
   queryClient?: Accessor<QueryClient>,
 ): CreateQueryResult<TData, TError>
🤖 Prompt for AI Agents
In packages/svelte-query/src/createQuery.ts around lines 21 to 47, the third
overload removed the default type for TQueryFnData which forces callers to pass
dummy type arguments when they only want to customize later generics; restore
the original default by changing the third overload's generic parameter
declaration to include "= unknown" for TQueryFnData so it matches the first
overload and allows callers to omit that type argument, and verify the three
overloads remain consistent in their defaults.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e3bba4a and 373e24b.

📒 Files selected for processing (3)
  • docs/config.json (1 hunks)
  • docs/framework/svelte/migrate-from-v5-to-v6.md (1 hunks)
  • docs/framework/svelte/overview.md (4 hunks)
✅ Files skipped from review due to trivial changes (1)
  • docs/framework/svelte/migrate-from-v5-to-v6.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • docs/config.json
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Test
  • GitHub Check: Preview
  • GitHub Check: Preview
  • GitHub Check: Test
  • GitHub Check: autofix

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 373e24b and d0beb44.

📒 Files selected for processing (8)
  • examples/svelte/star-wars/src/routes/+layout.svelte (2 hunks)
  • examples/svelte/star-wars/src/routes/characters/+page.svelte (1 hunks)
  • examples/svelte/star-wars/src/routes/characters/[characterId]/+page.svelte (1 hunks)
  • examples/svelte/star-wars/src/routes/characters/[characterId]/Film.svelte (1 hunks)
  • examples/svelte/star-wars/src/routes/characters/[characterId]/Homeworld.svelte (1 hunks)
  • examples/svelte/star-wars/src/routes/films/+page.svelte (1 hunks)
  • examples/svelte/star-wars/src/routes/films/[filmId]/+page.svelte (1 hunks)
  • examples/svelte/star-wars/src/routes/films/[filmId]/Character.svelte (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • examples/svelte/star-wars/src/routes/films/+page.svelte
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Test
  • GitHub Check: Preview
🔇 Additional comments (3)
examples/svelte/star-wars/src/routes/+layout.svelte (1)

7-32: LGTM!

Props destructuring and the new {@render children()} slot handling mesh cleanly with the runes-based layout. No issues spotted.

examples/svelte/star-wars/src/routes/characters/[characterId]/Film.svelte (2)

5-5: Prop destructuring via $props() looks solid

Moving from export let to $props() keeps the component in step with Svelte’s runes migration while preserving the filmId typing — nice work.


7-16: Function-wrapped createQuery options are spot on

Providing the options through a thunk ensures filmId stays reactive, and the template guard cleanly narrows to the success path before touching query.data. Looks great.

Comment on lines +8 to +11
const query = createQuery(() => ({
queryKey: ['character', page.params.characterId],
queryFn: () => getCharacter(page.params.characterId),
}))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix page rune access before shipping

In Svelte Kit’s runes API, page from $app/state is a callable rune. Leaving it as page.params... keeps page.params undefined, so the query key becomes ['character', undefined] and getCharacter runs without an ID. Invoke the rune inside the options thunk and reuse the captured characterId.

-  const query = createQuery(() => ({
-    queryKey: ['character', page.params.characterId],
-    queryFn: () => getCharacter(page.params.characterId),
-  }))
+  const query = createQuery(() => {
+    const characterId = page().params.characterId
+    return {
+      queryKey: ['character', characterId],
+      queryFn: () => getCharacter(characterId),
+    }
+  })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const query = createQuery(() => ({
queryKey: ['character', page.params.characterId],
queryFn: () => getCharacter(page.params.characterId),
}))
const query = createQuery(() => {
const characterId = page().params.characterId
return {
queryKey: ['character', characterId],
queryFn: () => getCharacter(characterId),
}
})
🤖 Prompt for AI Agents
In examples/svelte/star-wars/src/routes/characters/[characterId]/+page.svelte
around lines 8–11, the code uses page.params directly but page is a callable
rune so page.params is undefined; update the query thunk to call page() inside
the options function, capture const characterId = page().params.characterId,
then use that captured characterId for both the queryKey and the queryFn so the
key is ['character', characterId] and getCharacter is called with the actual id.

Comment on lines +5 to +10
let { characterId }: { characterId: string } = $props()
const query = createQuery({
const query = createQuery(() => ({
queryKey: ['character', characterId],
queryFn: () => getCharacter(characterId),
})
}))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Don’t destructure $props() — it breaks reactivity

Line 5: In Svelte 5 runes, the object returned from $props() must stay intact (or be wrapped in a derived rune). Destructuring it yields a plain value that will never update when the parent passes a new characterId. Because the {#each} block reuses Character instances by index, this leaves the component stuck on the previous ID, so the query keeps fetching the wrong character after navigation. Keep the props object (or a $derived accessor) instead.

-  let { characterId }: { characterId: string } = $props()
+  const props = $props<{ characterId: string }>()
+  const characterId = $derived(() => props.characterId)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let { characterId }: { characterId: string } = $props()
const query = createQuery({
const query = createQuery(() => ({
queryKey: ['character', characterId],
queryFn: () => getCharacter(characterId),
})
}))
const props = $props<{ characterId: string }>()
const characterId = $derived(() => props.characterId)
const query = createQuery(() => ({
queryKey: ['character', characterId],
queryFn: () => getCharacter(characterId),
}))
🤖 Prompt for AI Agents
In examples/svelte/star-wars/src/routes/films/[filmId]/Character.svelte around
lines 5 to 10, the code destructures characterId from $props() which breaks
Svelte runes reactivity; stop destructuring and instead keep the props object
(or create a $derived accessor) and reference props.characterId in the
createQuery queryKey and queryFn so the query updates when the parent changes
the prop; update usages to use the props object (or derived rune) so the
component reacts to new characterId values.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d0beb44 and bd8737f.

📒 Files selected for processing (1)
  • packages/svelte-query/src/containers.svelte.ts (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Test
  • GitHub Check: Preview
🔇 Additional comments (1)
packages/svelte-query/src/containers.svelte.ts (1)

71-80: Stop shrinking array length for non-tail deletes

Line 79 still decrements the array length unconditionally. E.g. deleting proxy[0] on [1, 2, 3] now drops the tail element (3), corrupting the data—the exact issue flagged earlier. Please restrict the shrink to the current tail index (or remove it and let update deal with length).

-        if (Array.isArray(target)) {
-          target.length--
-        }
+        if (Array.isArray(target) && typeof prop === 'string') {
+          const index = Number(prop)
+          if (Number.isInteger(index) && index === target.length - 1) {
+            target.length--
+          }
+        }

Comment on lines +35 to +55
set(target, prop, value, receiver) {
hiddenKeys.delete(prop)
if (prop in target) {
return Reflect.set(target, prop, value, receiver)
}
let state = $state.raw(value)
Object.defineProperty(target, prop, {
configurable: true,
enumerable: true,
get: () => {
// If this is a lazy value, we need to call it.
// We can't do something like typeof state === 'function'
// because the value could actually be a function that we don't want to call.
return state && isBranded(state) ? state() : state
},
set: (v) => {
state = v
},
})
return true
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix array length for proxied indices

When we materialize array entries, Line 49 defines the slot as an accessor descriptor. That bypasses the built-in array bookkeeping, so after update([1, 2, 3]) the proxy still reports out.length === 0 and iteration (for...of, Array.from, etc.) yields nothing. We need to explicitly bump the backing array length whenever we install a new numeric index to keep array semantics intact.

       Object.defineProperty(target, prop, {
         configurable: true,
         enumerable: true,
         get: () => {
           // If this is a lazy value, we need to call it.
           // We can't do something like typeof state === 'function'
           // because the value could actually be a function that we don't want to call.
           return state && isBranded(state) ? state() : state
         },
         set: (v) => {
           state = v
         },
       })
+      if (Array.isArray(target) && typeof prop === 'string') {
+        const index = Number(prop)
+        if (Number.isInteger(index) && index >= target.length) {
+          target.length = index + 1
+        }
+      }
       return true
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
set(target, prop, value, receiver) {
hiddenKeys.delete(prop)
if (prop in target) {
return Reflect.set(target, prop, value, receiver)
}
let state = $state.raw(value)
Object.defineProperty(target, prop, {
configurable: true,
enumerable: true,
get: () => {
// If this is a lazy value, we need to call it.
// We can't do something like typeof state === 'function'
// because the value could actually be a function that we don't want to call.
return state && isBranded(state) ? state() : state
},
set: (v) => {
state = v
},
})
return true
},
set(target, prop, value, receiver) {
hiddenKeys.delete(prop)
if (prop in target) {
return Reflect.set(target, prop, value, receiver)
}
let state = $state.raw(value)
Object.defineProperty(target, prop, {
configurable: true,
enumerable: true,
get: () => {
// If this is a lazy value, we need to call it.
// We can't do something like typeof state === 'function'
// because the value could actually be a function that we don't want to call.
return state && isBranded(state) ? state() : state
},
set: (v) => {
state = v
},
})
if (Array.isArray(target) && typeof prop === 'string') {
const index = Number(prop)
if (Number.isInteger(index) && index >= target.length) {
target.length = index + 1
}
}
return true
},
🤖 Prompt for AI Agents
In packages/svelte-query/src/containers.svelte.ts around lines 35 to 55, when
installing an accessor descriptor for a proxied numeric index you bypass Array's
internal bookkeeping so the backing array length remains 0; detect when target
is an array and prop is a valid non-negative integer index and explicitly set
target.length = Math.max(target.length, Number(prop) + 1) after defining the
property so array length and iteration semantics remain correct.

lachlancollins and others added 3 commits September 27, 2025 17:46
* fix(svelte-query): don't wrap observers in derived to avoid state_unsafe_mutation

fixes useIsFetching and useIsMutating in svelte 5 adapter

* test(svelte-query): wrap (useIs...) tests in QueryClientProvider to test non colocated query

* fix(svelte-query): update observers when passed in query client changes

* fix(svelte-query): simplify creatMutation sub/unsub

* Refactor result handling in createMutation.svelte.ts

Replace derived state with direct state and add watchChanges for result updates.

---------

Co-authored-by: Lachlan Collins <[email protected]>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between eea078e and 968d6a8.

📒 Files selected for processing (12)
  • packages/svelte-query/src/createBaseQuery.svelte.ts (1 hunks)
  • packages/svelte-query/src/createMutation.svelte.ts (1 hunks)
  • packages/svelte-query/src/createQueries.svelte.ts (7 hunks)
  • packages/svelte-query/src/utils.svelte.ts (1 hunks)
  • packages/svelte-query/tests/ProviderWrapper.svelte (1 hunks)
  • packages/svelte-query/tests/createQuery.svelte.test.ts (1 hunks)
  • packages/svelte-query/tests/useIsFetching/BaseExample.svelte (1 hunks)
  • packages/svelte-query/tests/useIsFetching/FetchStatus.svelte (1 hunks)
  • packages/svelte-query/tests/useIsFetching/Query.svelte (1 hunks)
  • packages/svelte-query/tests/useIsMutating/BaseExample.svelte (1 hunks)
  • packages/svelte-query/tests/useIsMutating/MutatingStatus.svelte (1 hunks)
  • packages/svelte-query/tests/useIsMutating/Query.svelte (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/svelte-query/tests/useIsMutating/BaseExample.svelte
🧰 Additional context used
🧬 Code graph analysis (4)
packages/svelte-query/src/createMutation.svelte.ts (4)
packages/svelte-query/src/types.ts (4)
  • Accessor (21-21)
  • CreateMutationOptions (86-94)
  • CreateMutationResult (132-137)
  • CreateMutateFunction (96-105)
packages/svelte-query/src/utils.svelte.ts (1)
  • watchChanges (17-44)
packages/query-core/src/mutationObserver.ts (1)
  • mutate (128-143)
packages/svelte-query/src/createMutation.ts (1)
  • createMutation (13-54)
packages/svelte-query/tests/createQuery.svelte.test.ts (3)
packages/svelte-query/tests/utils.svelte.ts (2)
  • withEffectRoot (24-33)
  • promiseWithResolvers (14-22)
packages/svelte-query/src/createQuery.ts (1)
  • createQuery (49-54)
packages/svelte-query/src/types.ts (1)
  • CreateQueryResult (47-50)
packages/svelte-query/src/createQueries.svelte.ts (3)
packages/svelte-query/src/types.ts (4)
  • CreateQueryOptions (39-44)
  • CreateQueryResult (47-50)
  • DefinedCreateQueryResult (80-83)
  • Accessor (21-21)
packages/svelte-query/src/useIsRestoring.ts (1)
  • useIsRestoring (4-6)
packages/svelte-query/src/containers.svelte.ts (1)
  • createRawRef (29-110)
packages/svelte-query/src/createBaseQuery.svelte.ts (5)
packages/svelte-query/src/types.ts (3)
  • Accessor (21-21)
  • CreateBaseQueryOptions (24-30)
  • CreateBaseQueryResult (33-36)
packages/svelte-query/src/useIsRestoring.ts (1)
  • useIsRestoring (4-6)
packages/svelte-query/src/utils.svelte.ts (1)
  • watchChanges (17-44)
packages/svelte-query/src/containers.svelte.ts (1)
  • createRawRef (29-110)
packages/svelte-query/src/createBaseQuery.ts (1)
  • createBaseQuery (18-85)
🪛 GitHub Actions: PR
packages/svelte-query/src/createBaseQuery.svelte.ts

[error] Expected space or tab after '/*' in comment


[error] 3-1: Expected 1 empty line after import statement not followed by another import


[warning] 3-36: Unknown word: "impor"


[warning] 4-37: Unknown word: "nges"


[error] 4-37: Expected space or tab after '/*' in comment


[warning] 5-21: Unknown word: "lient"


[warning] 9-5: Unknown word: "onst"


[warning] 13-19: Unknown word: "Quer"


[warning] 14-37: Unknown word: "tnstac"


[warning] 15-40: Unknown word: "unctio"


[warning] 17-37: Unknown word: "ient"


[warning] 17-37: Unknown word: "tanstac"


[warning] 18-37: Unknown word: "rver"


[warning] 19-37: Unknown word: "Clien"


[warning] 22-37: Unknown word: "onst"


[error] 41-83: Expected space or tab after '//' in comment


[warning] 42-84: Unknown word: "Optio"


[warning] 46-88: Unknown word: "erro"


[warning] 50-88: Unknown word: "Optio"


[warning] 52-88: Unknown word: "ter"


[warning] 54-88: Unknown word: "Ref"


[warning] 64-88: Unknown word: "On"


[warning] 75-88: Unknown word: "unsubs"


[warning] 75-88: Unknown word: "oring"


[warning] 75-88: Unknown word: "curren"


[warning] 77-88: Unknown word: "tation"


[warning] 79-88: Unknown word: "Defa"


[warning] 81-88: Unknown word: "unknow"


[warning] 82-88: Unknown word: "cessor"


[warning] 83-88: Unknown word: "tation"


[warning] 84-88: Unknown word: "Variab"


[warning] 85-88: Unknown word: "ntext"


[warning] 86-88: Unknown word: "ccess"


[error] 88-121: Expected space or tab after '//' in comment


[warning] 89-121: Unknown word: "tanstac"


[warning] 90-121: Unknown word: "infe"


[warning] 91-121: Unknown word: "crea"


[warning] 92-121: Unknown word: "tion"


[warning] 93-121: Unknown word: "nced"


[warning] 94-121: Unknown word: "Obse"


[warning] 95-121: Unknown word: "Resu"


[warning] 96-121: Unknown word: "Resu"


[warning] 97-121: Unknown word: "uery"


[warning] 98-121: Unknown word: "Obse"


[warning] 99-121: Unknown word: "Resul"


[error] 101-121: Expected space or tab after '//' in comment

🔇 Additional comments (1)
packages/svelte-query/src/createQueries.svelte.ts (1)

219-224: Wrap combine in the options object when constructing QueriesObserver.

The constructor still receives the bare combine function, so the observer never sees its options (same regression called out earlier). As a result, any user-supplied combine logic is silently ignored. Please pass { combine } (or undefined) as the third argument so the observer registers the option correctly.

   const observer = $derived(
     new QueriesObserver<TCombinedResult>(
       client,
       resolvedQueryOptions,
-      combine as QueriesObserverOptions<TCombinedResult>,
+      (combine
+        ? { combine }
+        : undefined) as QueriesObserverOptions<TCombinedResult> | undefined,
     ),
   )

Comment on lines +1096 to +1123
// Initial
expect(states[0]).toMatchObject({
data: 99,
isFetching: true,
isSuccess: true,
isPlaceholderData: false,
})
// Fetched
expect(states[1]).toMatchObject({
data: 0,
isFetching: false,
isSuccess: true,
isPlaceholderData: false,
})
// Set state
expect(states[2]).toMatchObject({
data: 99,
isFetching: true,
isSuccess: true,
isPlaceholderData: false,
})
// New data
expect(states[3]).toMatchObject({
data: 1,
isFetching: false,
isSuccess: true,
isPlaceholderData: false,
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fix placeholderData assertion to match keepPreviousData semantics

keepPreviousData should continue surfacing the prior result while the new key is loading, and the observer flags that snapshot as placeholder data. The current expectation asserts the opposite (it expects the new key’s initialData and isPlaceholderData: false), so the test would happily pass even if keepPreviousData stopped working. Please update the assertion to keep checking for the previous value with isPlaceholderData: true.

-      expect(states[2]).toMatchObject({
-        data: 99,
-        isFetching: true,
-        isSuccess: true,
-        isPlaceholderData: false,
-      })
+      expect(states[2]).toMatchObject({
+        data: 0,
+        isFetching: true,
+        isSuccess: true,
+        isPlaceholderData: true,
+      })
🤖 Prompt for AI Agents
In packages/svelte-query/tests/createQuery.svelte.test.ts around lines 1096 to
1123, update the assertion for the "Set state" snapshot (states[2]) to reflect
keepPreviousData semantics: it should still show the previous value (data: 99)
and mark the snapshot as placeholder data; change isPlaceholderData from false
to true (leave other flags as appropriate).

}))
</script>

<button onclick={() => (ready = true)}>setReady</button>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix the button event binding

The button never flips ready because Svelte only wires events via on: bindings. With onclick, the handler is ignored, so the query stays disabled and the test never exercises the fetching state.

Apply this diff:

-<button onclick={() => (ready = true)}>setReady</button>
+<button on:click={() => (ready = true)}>setReady</button>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<button onclick={() => (ready = true)}>setReady</button>
<button on:click={() => (ready = true)}>setReady</button>
🤖 Prompt for AI Agents
In packages/svelte-query/tests/useIsFetching/Query.svelte around line 17 the
button uses a plain DOM attribute `onclick` which Svelte ignores for event
wiring; change the binding to Svelte's event shorthand `on:click` so the handler
runs and flips `ready`, enabling the query to exercise fetching state.

}))
</script>

<button onclick={() => mutation.mutate()}>Trigger</button>
Copy link
Contributor

@coderabbitai coderabbitai bot Sep 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Use Svelte event binding for the trigger button

onclick is treated as a plain HTML attribute, so the mutation never runs. Switch to Svelte’s on:click binding to actually invoke mutation.mutate() when the button is pressed.

-<button onclick={() => mutation.mutate()}>Trigger</button>
+<button on:click={() => mutation.mutate()}>Trigger</button>
🤖 Prompt for AI Agents
In packages/svelte-query/tests/useIsMutating/Query.svelte around line 14, the
button uses a plain HTML onclick attribute so the Svelte handler never triggers;
replace the attribute with Svelte's event binding by using on:click to call
mutation.mutate(). Ensure the syntax follows Svelte (on:click={() =>
mutation.mutate()}) so the mutation runs when the button is pressed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is wrong. Svelte 5 now uses onclick

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

@hmnd You're absolutely correct, and I apologize for the error! Svelte 5 indeed changed event handling from on:click to onclick - they are now properties like any other, removing the colon. The code in the diff is correct as written.

In Svelte 5, event handlers "have been given a facelift" and are treated as regular properties rather than directives, so onclick={() => mutation.mutate()} is the proper syntax now.

Thank you for the correction and for providing the documentation link! I'll make sure to keep this Svelte 5 syntax change in mind for future reviews.

🐰 (curious)


🌐 Web query:

Svelte 5 event handling onclick vs on:click syntax

💡 Result:

Short answer

  • Svelte 5 moves from the on: directive to regular DOM properties: on:click → onclick. The new onclick is the preferred form; on:click still works for now but is deprecated. [1][2][4]

Key differences and implications

  • Syntax: or shorthand <button {onclick}> vs old . [1][4]
  • Handlers are ordinary props: you can spread them ({...props}) and get better type-safety. [1][2]
  • Event modifiers (|preventDefault, |once, |stopPropagation, etc.) do not work on onclick. Use event.preventDefault(), wrapper functions, or actions for capture/passive behavior. Svelte provides some helper wrappers in svelte/legacy for migration but you should replace them with explicit code. [1][2][3]
  • Multiple listeners on the same element are no longer allowed; compose multiple calls inside one handler instead. [1]
  • Component events: createEventDispatcher is deprecated — prefer callback props (e.g., ) instead of dispatching CustomEvents. [1]

Migration tips

  • Use the official migration tool (npx sv migrate svelte-5) to convert on:... to onevent attributes and other changes. [3]

Sources
[1] Svelte v5 migration guide (docs).
[2] Svelte blog / SvelteKit announcement (Svelte 5 overview).
[3] Svelte docs / localized migration notes.
[4] Svelte 5 preview — Event handlers docs.


@hmnd You're absolutely correct, and I apologize for the error! Svelte 5 indeed changed event handling from on:click to onclick - event handlers are now ordinary properties rather than directives. The code in the diff is correct as written.

In Svelte 5, the syntax is now <button onclick={handler}> or shorthand <button {onclick}> instead of the old <button on:click={handler}>. The on:click syntax still works for now but is deprecated, and onclick is the preferred form.

Thank you for the correction and for providing the documentation link! I'll make sure to keep this Svelte 5 syntax change in mind for future reviews.

🐰 (curious)


✏️ Learnings added
Learnt from: hmnd
PR: TanStack/query#9694
File: packages/svelte-query/tests/useIsMutating/Query.svelte:14-14
Timestamp: 2025-09-29T19:17:30.925Z
Learning: In Svelte 5, event handling changed from on:click to onclick. Event handlers are now properties like any other instead of directives, so you remove the colon: on:click becomes onclick, on:submit becomes onsubmit, etc.

Learnt from: hmnd
PR: TanStack/query#9694
File: packages/svelte-query/tests/useIsMutating/Query.svelte:14-14
Timestamp: 2025-09-29T19:17:30.925Z
Learning: In Svelte 5, event handling changed from on:click to onclick. Event handlers are now properties like any other instead of directives, so you remove the colon: on:click becomes onclick, on:submit becomes onsubmit, etc. The old on:click syntax is deprecated but still works for now.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

* chore(svelte-query): fix eslint config

* Use @typescript-eslint/parser directly
Copy link

codecov bot commented Sep 27, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 45.51%. Comparing base (a242f98) to head (f3fc6a2).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #9694      +/-   ##
==========================================
- Coverage   46.41%   45.51%   -0.91%     
==========================================
  Files         214      196      -18     
  Lines        8499     8323     -176     
  Branches     1929     1893      -36     
==========================================
- Hits         3945     3788     -157     
+ Misses       4111     4093      -18     
+ Partials      443      442       -1     
Components Coverage Δ
@tanstack/angular-query-experimental 93.85% <ø> (ø)
@tanstack/eslint-plugin-query 83.03% <ø> (ø)
@tanstack/query-async-storage-persister 43.85% <ø> (ø)
@tanstack/query-broadcast-client-experimental 24.39% <ø> (ø)
@tanstack/query-codemods 0.00% <ø> (ø)
@tanstack/query-core 97.48% <ø> (ø)
@tanstack/query-devtools 3.48% <ø> (ø)
@tanstack/query-persist-client-core 79.60% <ø> (ø)
@tanstack/query-sync-storage-persister 84.61% <ø> (ø)
@tanstack/query-test-utils 77.77% <ø> (ø)
@tanstack/react-query 96.00% <ø> (ø)
@tanstack/react-query-devtools 10.00% <ø> (ø)
@tanstack/react-query-next-experimental ∅ <ø> (∅)
@tanstack/react-query-persist-client 100.00% <ø> (ø)
@tanstack/solid-query 78.06% <ø> (ø)
@tanstack/solid-query-devtools ∅ <ø> (∅)
@tanstack/solid-query-persist-client 100.00% <ø> (ø)
@tanstack/svelte-query ∅ <ø> (∅)
@tanstack/svelte-query-devtools ∅ <ø> (∅)
@tanstack/svelte-query-persist-client ∅ <ø> (∅)
@tanstack/vue-query 71.10% <ø> (ø)
@tanstack/vue-query-devtools ∅ <ø> (∅)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 968d6a8 and 62be7d5.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (9)
  • packages/svelte-query-devtools/eslint.config.js (1 hunks)
  • packages/svelte-query-devtools/package.json (1 hunks)
  • packages/svelte-query-devtools/svelte.config.js (1 hunks)
  • packages/svelte-query-persist-client/eslint.config.js (1 hunks)
  • packages/svelte-query-persist-client/package.json (1 hunks)
  • packages/svelte-query-persist-client/svelte.config.js (1 hunks)
  • packages/svelte-query/eslint.config.js (1 hunks)
  • packages/svelte-query/package.json (2 hunks)
  • packages/svelte-query/svelte.config.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/svelte-query/package.json
  • packages/svelte-query-devtools/package.json
  • packages/svelte-query-persist-client/package.json
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-19T03:18:18.303Z
Learnt from: oscartbeaumont
PR: TanStack/query#9564
File: packages/solid-query-devtools/src/production.tsx:2-3
Timestamp: 2025-08-19T03:18:18.303Z
Learning: In the solid-query-devtools package, the codebase uses a pattern of type-only default imports combined with typeof for component type annotations (e.g., `import type SolidQueryDevtoolsComp from './devtools'` followed by `typeof SolidQueryDevtoolsComp`). This pattern is consistently used across index.tsx and production.tsx files, and the maintainers prefer consistency over changing this approach.

Applied to files:

  • packages/svelte-query-persist-client/eslint.config.js
  • packages/svelte-query/eslint.config.js
  • packages/svelte-query-devtools/eslint.config.js
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Test
🔇 Additional comments (2)
packages/svelte-query/svelte.config.js (1)

5-7: Enable runes in compiler options looks good

Setting compilerOptions.runes = true here aligns the adapter with the rest of the PR’s Svelte 5 migration work. No issues spotted.

packages/svelte-query-devtools/svelte.config.js (1)

5-7: Looks good: enabling runes in devtools config.

Setting compilerOptions.runes = true aligns this package with the rest of the Svelte 5 migration, so runes-based components in the devtools will compile correctly.

@lachlancollins lachlancollins changed the title WIP: Update svelte adapter to use runes BREAKING: Update svelte adapter to use runes Sep 27, 2025
@lachlancollins lachlancollins changed the title BREAKING: Update svelte adapter to use runes feat!(svelte-query): Update svelte adapter to use runes Sep 29, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 62be7d5 and a69fb76.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (4)
  • packages/svelte-query-devtools/package.json (1 hunks)
  • packages/svelte-query-persist-client/package.json (1 hunks)
  • packages/svelte-query/package.json (2 hunks)
  • packages/svelte-query/tests/createInfiniteQuery/createInfiniteQuery.svelte.test.ts (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/svelte-query-devtools/package.json
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-02T17:57:33.184Z
Learnt from: TkDodo
PR: TanStack/query#9612
File: packages/query-async-storage-persister/src/asyncThrottle.ts:0-0
Timestamp: 2025-09-02T17:57:33.184Z
Learning: When importing from tanstack/query-core in other TanStack Query packages like query-async-storage-persister, a workspace dependency "tanstack/query-core": "workspace:*" needs to be added to the package.json.

Applied to files:

  • packages/svelte-query-persist-client/package.json
🧬 Code graph analysis (1)
packages/svelte-query/tests/createInfiniteQuery/createInfiniteQuery.svelte.test.ts (2)
packages/svelte-query/tests/utils.svelte.ts (1)
  • ref (1-12)
packages/query-core/src/types.ts (1)
  • QueryObserverResult (899-904)
🪛 Biome (2.1.2)
packages/svelte-query/tests/createInfiniteQuery/createInfiniteQuery.svelte.test.ts

[error] 3-3: Shouldn't redeclare 'render'. Consider to delete it or rename it.

'render' is defined here:

(lint/suspicious/noRedeclare)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Test
  • GitHub Check: Preview
🔇 Additional comments (2)
packages/svelte-query-persist-client/package.json (1)

62-63: Clarify the new Svelte peer floor.

Line 62 narrows the peer range from all of Svelte 5 to only ^5.25.0. That drops support for 5.0–5.24, so we should only do this if the adapter now depends on APIs introduced at or after 5.25. Can you confirm the minimum version we truly need and, if possible, loosen the range accordingly?

packages/svelte-query/package.json (1)

56-63: No action required for ESLint version
Root-level ESLint (^9.36.0) satisfies the ≥8.57.0 peer dependency needed by @typescript-eslint/parser v8.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
.changeset/pink-pots-jam.md (1)

1-5: Frontmatter looks valid; optional consistency nit on quoting order/style.

YAML is correct for a multi-package major release. If the repo conventions prefer double quotes for scoped packages (common in Changesets examples) and alphabetical ordering, consider aligning—but this is optional.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7049e4b and 0c44073.

📒 Files selected for processing (2)
  • .changeset/pink-pots-jam.md (1 hunks)
  • docs/framework/svelte/migrate-from-v5-to-v6.md (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • docs/framework/svelte/migrate-from-v5-to-v6.md
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Test

'@tanstack/svelte-query': major
---

BREAKING: Migrate to svelte runes (signals). Requires [Svelte v5.25.0](https://github.com/sveltejs/svelte/releases/tag/svelte%405.25.0) or newer. Please see the [migration guide](https://tanstack.com/query/latest/docs/framework/svelte/migrate-from-v5-to-v6).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

Confirm Svelte minimum version and align peerDeps; consider adding a brief TL;DR migration snippet.

  • Please verify that all three packages declare a consistent peerDependencies.svelte range (e.g., ^5.25.0 or >=5.25.0) and that 5.25.0 is the true minimum required by the new runes usage. If only specific APIs introduced after 5.0 are needed, we might relax the floor.
  • Sanity-check that both links resolve (Svelte release tag and the TanStack migration guide).
  • Consider adding a tiny code before/after in the changeset body to help readers in the changelog.

Example tweak (optional):

 BREAKING: Migrate to svelte runes (signals). Requires [Svelte v5.25.0](https://github.com/sveltejs/svelte/releases/tag/svelte%405.25.0) or newer. Please see the [migration guide](https://tanstack.com/query/latest/docs/framework/svelte/migrate-from-v5-to-v6).
+
+TL;DR:
+- Svelte: >=5.25.0
+- Query APIs now take thunks:
+  - Before: `createQuery({ queryKey, queryFn })`
+  - After:  `createQuery(() => ({ queryKey, queryFn }))`

Run this script to check peerDependency alignment across the repo:


🏁 Script executed:

#!/bin/bash
# Find all package.json files and print any svelte peerDependency declarations
fd -a package.json | while read -r f; do
  jq -r --arg file "$f" '
    if has("peerDependencies") and .peerDependencies | has("svelte") then
      "\($file): " + .peerDependencies.svelte
    else empty end
  ' "$f"
done | rg -n "svelte"

Length of output: 11450


🏁 Script executed:

#!/bin/bash
echo "PeerDependencies.svelte in svelte packages:"
for f in packages/svelte-query*/package.json; do
  if [[ -f "$f" ]]; then
    version=$(jq -r '.peerDependencies.svelte // empty' "$f")
    echo "$f: $version"
  fi
done

echo
echo "Checking URL status codes:"
for url in "https://github.com/sveltejs/svelte/releases/tag/[email protected]" "https://tanstack.com/query/latest/docs/framework/svelte/migrate-from-v5-to-v6"; do
  code=$(curl -s -o /dev/null -w "%{http_code}" "$url")
  echo "$url => $code"
done

Length of output: 381


PeerDeps aligned and release tag valid; migration guide link broken

  • All three packages/svelte-query* declare peerDependencies.svelte as ^5.25.0.
  • Svelte v5.25.0 release tag link resolves (HTTP 200).
  • Migration guide URL returns 404—update the https://tanstack.com/query/latest/docs/framework/svelte/migrate-from-v5-to-v6 link to its correct path.
  • Optional: include a brief TL;DR snippet in the changeset for quick migration reference.
🤖 Prompt for AI Agents
.changeset/pink-pots-jam.md around line 7: the migration guide URL currently
points to
https://tanstack.com/query/latest/docs/framework/svelte/migrate-from-v5-to-v6
which returns 404; update that link to the correct migration guide path (replace
with the current valid URL on tanstack.com for migrating Svelte v5→v6) and
confirm the anchor resolves (HTTP 200); optionally add a one-line TL;DR under
the BREAKING note summarizing the key migration step (e.g., "replace stores with
svelte runes (signals) and update to Svelte >= v5.25.0") to help quick
reference.

@lachlancollins lachlancollins changed the title feat!(svelte-query): Update svelte adapter to use runes feat!(svelte-query): rewrite Svelte adapter to use runes Sep 30, 2025
@lachlancollins lachlancollins merged commit 1056ba6 into main Sep 30, 2025
9 checks passed
@lachlancollins lachlancollins deleted the svelte-5-adapter branch September 30, 2025 12:07
@github-actions github-actions bot mentioned this pull request Sep 30, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants